commit 1e7b1753d4f32ed3f4ad7a523a41c455b672ce43
parent 928a253e9e58046a59d1a300a62f94f34da67a0b
Author: Andrea Marchesini <amarchesini@mozilla.com>
Date: Wed, 3 Dec 2025 09:45:35 +0000
Bug 1986320 - Implement an add-on block URL-Classifier feature - part 4 - Expose the URLClassifier addon feature to non-recommended addons only, r=zombie
Differential Revision: https://phabricator.services.mozilla.com/D263546
Diffstat:
7 files changed, 54 insertions(+), 0 deletions(-)
diff --git a/dom/chrome-webidl/WebExtensionPolicy.webidl b/dom/chrome-webidl/WebExtensionPolicy.webidl
@@ -126,6 +126,12 @@ interface WebExtensionPolicy {
attribute boolean ignoreQuarantine;
/**
+ * True if this extension has recommended state.
+ */
+ [Cached, Pure]
+ readonly attribute boolean hasRecommendedState;
+
+ /**
* True if both e10s and webextensions.remote are enabled. This must be
* used instead of checking the remote pref directly since remote extensions
* require both to be enabled.
@@ -324,6 +330,8 @@ dictionary WebExtensionInit {
boolean ignoreQuarantine = false;
+ boolean hasRecommendedState = false;
+
boolean temporarilyInstalled = false;
required WebExtensionLocalizeCallback localizeCallback;
diff --git a/netwerk/url-classifier/UrlClassifierFeatureHarmfulAddonProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureHarmfulAddonProtection.cpp
@@ -211,6 +211,12 @@ UrlClassifierFeatureHarmfulAddonProtection::MaybeCreate(nsIChannel* aChannel) {
return nullptr;
}
+ // Recommended add-ons are exempt.
+ extensions::WebExtensionPolicy* policy = GetAddonPolicy(aChannel);
+ if (policy && policy->HasRecommendedState()) {
+ return nullptr;
+ }
+
MaybeInitialize();
MOZ_ASSERT(gFeatureHarmfulAddonProtection);
diff --git a/toolkit/components/extensions/Extension.sys.mjs b/toolkit/components/extensions/Extension.sys.mjs
@@ -3469,6 +3469,8 @@ export class Extension extends ExtensionData {
!!addonData.recommendationState?.states?.length ||
lazy.QuarantinedDomains.isUserAllowedAddonId(this.id);
+ this.hasRecommendedState = !!addonData.recommendationState?.states?.length;
+
this.views = new Set();
this._backgroundPageFrameLoader = null;
@@ -3799,6 +3801,7 @@ export class Extension extends ExtensionData {
optionalPermissions: this.optionalPermissions,
isPrivileged: this.isPrivileged,
ignoreQuarantine: this.ignoreQuarantine,
+ hasRecommendedState: this.hasRecommendedState,
temporarilyInstalled: this.temporarilyInstalled,
};
}
@@ -4073,6 +4076,7 @@ export class Extension extends ExtensionData {
baseURL: this.resourceURL,
isPrivileged: this.isPrivileged,
ignoreQuarantine: this.ignoreQuarantine,
+ hasRecommendedState: this.hasRecommendedState,
temporarilyInstalled: this.temporarilyInstalled,
allowedOrigins: new MatchPatternSet([]),
localizeCallback: () => "",
@@ -4089,6 +4093,7 @@ export class Extension extends ExtensionData {
baseURL: this.resourceURL,
isPrivileged: this.isPrivileged,
ignoreQuarantine: this.ignoreQuarantine,
+ hasRecommendedState: this.hasRecommendedState,
});
sharedData.set("extensions/pending", pendingExtensions);
diff --git a/toolkit/components/extensions/ExtensionProcessScript.sys.mjs b/toolkit/components/extensions/ExtensionProcessScript.sys.mjs
@@ -145,6 +145,7 @@ ExtensionManager = {
isPrivileged: extension.isPrivileged,
ignoreQuarantine: extension.ignoreQuarantine,
+ hasRecommendedState: extension.hasRecommendedState,
temporarilyInstalled: extension.temporarilyInstalled,
permissions: extension.permissions,
allowedOrigins: extension.allowedOrigins,
diff --git a/toolkit/components/extensions/WebExtensionPolicy.cpp b/toolkit/components/extensions/WebExtensionPolicy.cpp
@@ -197,6 +197,7 @@ WebExtensionPolicyCore::WebExtensionPolicyCore(GlobalObject& aGlobal,
mTemporarilyInstalled(aInit.mTemporarilyInstalled),
mBackgroundWorkerScript(aInit.mBackgroundWorkerScript),
mIgnoreQuarantine(aInit.mIsPrivileged || aInit.mIgnoreQuarantine),
+ mHasRecommendedState(aInit.mHasRecommendedState),
mPermissions(new AtomSet(aInit.mPermissions)) {
// In practice this is not necessary, but in tests where the uuid
// passed in is not lowercased various tests can fail.
@@ -468,6 +469,10 @@ void WebExtensionPolicy::SetIgnoreQuarantine(bool aIgnore) {
mCore->SetIgnoreQuarantine(aIgnore);
}
+void WebExtensionPolicy::SetHasRecommendedState(bool aHasRecommendedState) {
+ mCore->SetHasRecommendedState(aHasRecommendedState);
+}
+
void WebExtensionPolicy::RegisterContentScript(
WebExtensionContentScript& script, ErrorResult& aRv) {
// Raise an "invalid argument" error if the script is not related to
diff --git a/toolkit/components/extensions/WebExtensionPolicy.h b/toolkit/components/extensions/WebExtensionPolicy.h
@@ -144,6 +144,16 @@ class WebExtensionPolicyCore final {
bool QuarantinedFromDoc(const DocInfo& aDoc) const;
bool QuarantinedFromURI(const URLInfo& aURI) const MOZ_EXCLUDES(mLock);
+ bool HasRecommendedState() const MOZ_EXCLUDES(mLock) {
+ AutoReadLock lock(mLock);
+ return mHasRecommendedState;
+ }
+
+ void SetHasRecommendedState(bool aHasRecommendedState) MOZ_EXCLUDES(mLock) {
+ AutoWriteLock lock(mLock);
+ mHasRecommendedState = aHasRecommendedState;
+ }
+
bool PrivateBrowsingAllowed() const;
// Try to get a reference to the cycle-collected main-thread-only
@@ -194,6 +204,7 @@ class WebExtensionPolicyCore final {
mutable RWLock mLock{"WebExtensionPolicyCore"};
bool mIgnoreQuarantine MOZ_GUARDED_BY(mLock);
+ bool mHasRecommendedState MOZ_GUARDED_BY(mLock);
RefPtr<AtomSet> mPermissions MOZ_GUARDED_BY(mLock);
RefPtr<MatchPatternSetCore> mHostPermissions MOZ_GUARDED_BY(mLock);
};
@@ -315,6 +326,9 @@ class WebExtensionPolicy final : public nsISupports, public nsWrapperCache {
bool IgnoreQuarantine() const { return mCore->IgnoreQuarantine(); }
void SetIgnoreQuarantine(bool aIgnore);
+ bool HasRecommendedState() const { return mCore->HasRecommendedState(); }
+ void SetHasRecommendedState(bool aHasRecommendedState);
+
void GetContentScripts(ScriptArray& aScripts) const;
const ScriptArray& ContentScripts() const { return mContentScripts; }
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_recommendations.js b/toolkit/mozapps/extensions/test/xpcshell/test_recommendations.js
@@ -700,6 +700,21 @@ add_task(
extensionLine.awaitStartup(),
]);
+ Assert.equal(
+ WebExtensionPolicy.getByID(extensionInvalidRecommended.id)
+ .hasRecommendedState,
+ false
+ );
+ Assert.equal(
+ WebExtensionPolicy.getByID(extensionValidRecommended.id)
+ .hasRecommendedState,
+ true
+ );
+ Assert.equal(
+ WebExtensionPolicy.getByID(extensionLine.id).hasRecommendedState,
+ true
+ );
+
// Uninstall all test extensions.
await Promise.all(
Object.keys(recommendationStatesPerId).map(async addonId => {