summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/mozapps/extensions/test/xpcshell/rs-blocklist
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpcshell/rs-blocklist')
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/head.js120
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_android_blocklist_dump.js120
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_addonBlockURL.js56
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_appversion.js293
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_clients.js228
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_gfx.js113
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_metadata_filters.js116
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf.js267
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_dump.js156
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_fetch.js231
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_stashes.js219
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_telemetry.js188
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_update.js75
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_osabi.js286
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_prefs.js106
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_regexp_split.js225
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_severities.js504
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js411
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_targetapp_filter.js392
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_telemetry.js138
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange.js1410
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange_v2.js13
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Device.js73
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_DriverNew.js67
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverNew.js112
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverOld.js68
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_OK.js68
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_DriverOld.js68
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_OK.js70
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_No_Comparison.js69
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OK.js69
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OS.js68
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_match.js70
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js70
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js71
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Vendor.js68
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Version.js190
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_prefs.js124
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_softblocked.js61
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/xpcshell.ini67
40 files changed, 7120 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/head.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/head.js
new file mode 100644
index 0000000000..4007ec6988
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/head.js
@@ -0,0 +1,120 @@
+// Appease eslint.
+/* import-globals-from ../head_addons.js */
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+// Initializing and asserting the expected telemetry is currently conditioned
+// on this const.
+// TODO(Bug 1752139) remove this along with initializing and asserting the expected
+// telemetry also for android build, once `Services.fog.testResetFOG()` is implemented
+// for Android builds.
+const IS_ANDROID_BUILD = AppConstants.platform === "android";
+const IS_FOG_RESET_SUPPORTED = !IS_ANDROID_BUILD;
+const DUMMY_TIME = 939420000000; // new Date(1999, 9, 9)
+const DUMMY_STRING = "GleanDummyString";
+let gleanEventCount = 0;
+
+function _resetMetric(gleanMetric) {
+ let value = gleanMetric.testGetValue();
+ if (value === undefined) {
+ return; // Never initialized, nothing to reset.
+ }
+ if (gleanMetric instanceof Ci.nsIGleanDatetime) {
+ gleanMetric.set(DUMMY_TIME * 1000);
+ } else if (gleanMetric instanceof Ci.nsIGleanString) {
+ gleanMetric.set(DUMMY_STRING);
+ } else if (gleanMetric instanceof Ci.nsIGleanEvent) {
+ // NOTE: this doesn't work when there is more than one event;
+ // for now we assume that there is only one: addonBlockChange.
+ // Cannot overwrite, so just store the current list length.
+ gleanEventCount = value.length;
+ } else {
+ throw new Error("Unsupported Glean metric type - cannot reset");
+ }
+}
+
+function testGetValue(gleanMetric) {
+ let value = gleanMetric.testGetValue();
+ if (value === undefined || IS_FOG_RESET_SUPPORTED) {
+ return value;
+ }
+ if (gleanMetric instanceof Ci.nsIGleanDatetime) {
+ return value.getTime() === DUMMY_TIME ? undefined : value;
+ }
+ if (gleanMetric instanceof Ci.nsIGleanString) {
+ return value === DUMMY_STRING ? undefined : value;
+ }
+ if (gleanMetric instanceof Ci.nsIGleanEvent) {
+ // NOTE: this doesn't work when there is more than one event;
+ // for now we assume that there is only one: addonBlockChange.
+ value = value.slice(gleanEventCount);
+ return value.length ? value : undefined;
+ }
+ throw new Error("Unsupported Glean metric type");
+}
+
+function resetBlocklistTelemetry() {
+ if (IS_FOG_RESET_SUPPORTED) {
+ Services.fog.testResetFOG();
+ return;
+ }
+ // TODO bug 1752139: fix testResetFOG and remove workarounds.
+ _resetMetric(Glean.blocklist.addonBlockChange);
+ _resetMetric(Glean.blocklist.lastModifiedRsAddonsMblf);
+ _resetMetric(Glean.blocklist.mlbfSource);
+ _resetMetric(Glean.blocklist.mlbfGenerationTime);
+ _resetMetric(Glean.blocklist.mlbfStashTimeOldest);
+ _resetMetric(Glean.blocklist.mlbfStashTimeNewest);
+}
+
+const MLBF_RECORD = {
+ id: "A blocklist entry that refers to a MLBF file",
+ // Higher than any last_modified in addons-bloomfilters.json:
+ last_modified: Date.now(),
+ attachment: {
+ size: 32,
+ hash: "6af648a5d6ce6dbee99b0aab1780d24d204977a6606ad670d5372ef22fac1052",
+ filename: "does-not-matter.bin",
+ },
+ attachment_type: "bloomfilter-base",
+ generation_time: 1577833200000,
+};
+
+function enable_blocklist_v2_instead_of_useMLBF() {
+ Blocklist.allowDeprecatedBlocklistV2 = true;
+ Services.prefs.setBoolPref("extensions.blocklist.useMLBF", false);
+ // Sanity check: blocklist v2 has been enabled.
+ const { BlocklistPrivate } = ChromeUtils.importESModule(
+ "resource://gre/modules/Blocklist.sys.mjs"
+ );
+ Assert.equal(
+ Blocklist.ExtensionBlocklist,
+ BlocklistPrivate.ExtensionBlocklistRS,
+ "ExtensionBlocklistRS should have been enabled"
+ );
+}
+
+async function load_mlbf_record_as_blob() {
+ const url = Services.io.newFileURI(
+ do_get_file("../data/mlbf-blocked1-unblocked2.bin")
+ ).spec;
+ Cu.importGlobalProperties(["fetch"]);
+ return (await fetch(url)).blob();
+}
+
+function getExtensionBlocklistMLBF() {
+ // ExtensionBlocklist.Blocklist is an ExtensionBlocklistMLBF if the useMLBF
+ // pref is set to true.
+ const {
+ BlocklistPrivate: { ExtensionBlocklistMLBF },
+ } = ChromeUtils.importESModule("resource://gre/modules/Blocklist.sys.mjs");
+ if (Blocklist.allowDeprecatedBlocklistV2) {
+ Assert.ok(
+ Services.prefs.getBoolPref("extensions.blocklist.useMLBF", false),
+ "blocklist.useMLBF should be true"
+ );
+ }
+ return ExtensionBlocklistMLBF;
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_android_blocklist_dump.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_android_blocklist_dump.js
new file mode 100644
index 0000000000..c0221c446b
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_android_blocklist_dump.js
@@ -0,0 +1,120 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Blocklist v3 will be enabled on release in bug 1824863.
+// TODO bug 1824863: Remove this when blocklist v3 is enabled.
+const IS_USING_BLOCKLIST_V3 = AppConstants.NIGHTLY_BUILD;
+
+// When bug 1639050 is fixed, this whole test can be removed as it is already
+// covered by test_blocklist_mlbf_dump.js.
+
+// A known blocked version from bug 1626602.
+// Same as in test_blocklist_mlbf_dump.js.
+const blockedAddon = {
+ id: "{6f62927a-e380-401a-8c9e-c485b7d87f0d}",
+ version: "9.2.0",
+ signedDate: new Date(1588098908496), // 2020-04-28 (dummy date)
+ signedState: AddonManager.SIGNEDSTATE_SIGNED,
+};
+
+// A known blocked version from bug 1681884, blocklist v3 only but not v2,
+// i.e. not listed in services/settings/dumps/blocklists/addons.json.
+const blockedAddonV3only = {
+ id: "{011f65f0-7143-470a-83ca-20ec4297f3f4}",
+ version: "1.0",
+ // omiting signedDate/signedState: in blocklist v2 those don't matter.
+ // In v3 those do matter, so if blocklist v3 were to be enabled, then
+ // the test would fail.
+};
+
+// A known add-on that is not blocked, as of writing. It is likely not going
+// to be blocked because it does not have any executable code.
+// Same as in test_blocklist_mlbf_dump.js.
+const nonBlockedAddon = {
+ id: "disable-ctrl-q-and-cmd-q@robwu.nl",
+ version: "1",
+ signedDate: new Date(1482430349000), // 2016-12-22 (actual signing time).
+ signedState: AddonManager.SIGNEDSTATE_SIGNED,
+};
+
+add_task(
+ { skip_if: () => IS_USING_BLOCKLIST_V3 },
+ async function verify_blocklistv2_dump_first_run() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(blockedAddon),
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "A add-on that is known to be on the v2 blocklist should be blocked"
+ );
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(blockedAddonV3only),
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "An add-on that is not part of the v2 blocklist should not be blocked"
+ );
+
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(nonBlockedAddon),
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "A known non-blocked add-on should not be blocked"
+ );
+ }
+);
+
+add_task(
+ { skip_if: () => !IS_USING_BLOCKLIST_V3 },
+ async function verify_a_known_blocked_add_on_is_not_detected_as_blocked_at_first_run() {
+ const MLBF_LOAD_RESULTS = [];
+ const MLBF_LOAD_ATTEMPTS = [];
+ const onLoadAttempts = record => MLBF_LOAD_ATTEMPTS.push(record);
+ const onLoadResult = promise => MLBF_LOAD_RESULTS.push(promise);
+ spyOnExtensionBlocklistMLBF(onLoadAttempts, onLoadResult);
+
+ // The addons blocklist data is not packaged and will be downloaded after install
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(blockedAddon),
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "A known blocked add-on should not be blocked at first"
+ );
+
+ await Assert.rejects(
+ MLBF_LOAD_RESULTS[0],
+ /DownloadError: Could not download addons-mlbf.bin/,
+ "Should not find any packaged attachment"
+ );
+
+ MLBF_LOAD_ATTEMPTS.length = 0;
+ MLBF_LOAD_RESULTS.length = 0;
+
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(blockedAddon),
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "Blocklist is still not populated"
+ );
+ Assert.deepEqual(
+ MLBF_LOAD_ATTEMPTS,
+ [],
+ "MLBF is not fetched again after the first lookup"
+ );
+ }
+);
+
+function spyOnExtensionBlocklistMLBF(onLoadAttempts, onLoadResult) {
+ const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF();
+ // Tapping into the internals of ExtensionBlocklistMLBF._fetchMLBF to observe
+ const originalFetchMLBF = ExtensionBlocklistMLBF._fetchMLBF;
+ ExtensionBlocklistMLBF._fetchMLBF = async function (record) {
+ onLoadAttempts(record);
+ let promise = originalFetchMLBF.apply(this, arguments);
+ onLoadResult(promise);
+ return promise;
+ };
+
+ registerCleanupFunction(
+ () => (ExtensionBlocklistMLBF._fetchMLBF = originalFetchMLBF)
+ );
+
+ return ExtensionBlocklistMLBF;
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_addonBlockURL.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_addonBlockURL.js
new file mode 100644
index 0000000000..b11d1329cd
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_addonBlockURL.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// useMLBF=true case is covered by test_blocklist_mlbf.js
+enable_blocklist_v2_instead_of_useMLBF();
+
+const BLOCKLIST_DATA = [
+ {
+ id: "foo",
+ guid: "myfoo",
+ versionRange: [
+ {
+ severity: "3",
+ },
+ ],
+ },
+ {
+ blockID: "bar",
+ // we'll get a uuid as an `id` property from loadBlocklistRawData
+ guid: "mybar",
+ versionRange: [
+ {
+ severity: "3",
+ },
+ ],
+ },
+];
+
+const BASE_BLOCKLIST_INFOURL = Services.prefs.getStringPref(
+ "extensions.blocklist.detailsURL"
+);
+
+/*
+ * Check that add-on blocklist URLs are correctly exposed
+ * based on either blockID or id properties on the entries
+ * in remote settings.
+ */
+add_task(async function blocklistURL_check() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+ await promiseStartupManager();
+ await AddonTestUtils.loadBlocklistRawData({ extensions: BLOCKLIST_DATA });
+
+ let entry = await Blocklist.getAddonBlocklistEntry({
+ id: "myfoo",
+ version: "1.0",
+ });
+ Assert.equal(entry.url, BASE_BLOCKLIST_INFOURL + "foo.html");
+
+ entry = await Blocklist.getAddonBlocklistEntry({
+ id: "mybar",
+ version: "1.0",
+ });
+ Assert.equal(entry.url, BASE_BLOCKLIST_INFOURL + "bar.html");
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_appversion.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_appversion.js
new file mode 100644
index 0000000000..e8d03f088b
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_appversion.js
@@ -0,0 +1,293 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// useMLBF=true does not offer special support for filtering by application ID.
+// The same functionality is offered through filter_expression, which is tested
+// by services/settings/test/unit/test_remote_settings_jexl_filters.js and
+// test_blocklistchange.js.
+enable_blocklist_v2_instead_of_useMLBF();
+
+var ADDONS = [
+ {
+ id: "test_bug449027_1@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 1",
+ version: "5",
+ start: false,
+ appBlocks: false,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_2@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 2",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_3@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 3",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_4@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 4",
+ version: "5",
+ start: false,
+ appBlocks: false,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_5@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 5",
+ version: "5",
+ start: false,
+ appBlocks: false,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_6@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 6",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_7@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 7",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_8@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 8",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_9@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 9",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_10@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 10",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_11@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 11",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_12@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 12",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_13@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 13",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_14@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 14",
+ version: "5",
+ start: false,
+ appBlocks: false,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_15@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 15",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: true,
+ },
+ {
+ id: "test_bug449027_16@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 16",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: true,
+ },
+ {
+ id: "test_bug449027_17@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 17",
+ version: "5",
+ start: false,
+ appBlocks: false,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_18@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 18",
+ version: "5",
+ start: false,
+ appBlocks: false,
+ toolkitBlocks: false,
+ },
+ {
+ id: "test_bug449027_19@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 19",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: true,
+ },
+ {
+ id: "test_bug449027_20@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 20",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: true,
+ },
+ {
+ id: "test_bug449027_21@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 21",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: true,
+ },
+ {
+ id: "test_bug449027_22@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 22",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: true,
+ },
+ {
+ id: "test_bug449027_23@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 23",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: true,
+ },
+ {
+ id: "test_bug449027_24@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 24",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: true,
+ },
+ {
+ id: "test_bug449027_25@tests.mozilla.org",
+ name: "Bug 449027 Addon Test 25",
+ version: "5",
+ start: false,
+ appBlocks: true,
+ toolkitBlocks: true,
+ },
+];
+
+function createAddon(addon) {
+ return promiseInstallWebExtension({
+ manifest: {
+ name: addon.name,
+ version: addon.version,
+ browser_specific_settings: { gecko: { id: addon.id } },
+ },
+ });
+}
+
+/**
+ * Checks that items are blocklisted correctly according to the current test.
+ * If a lastTest is provided checks that the notification dialog got passed
+ * the newly blocked items compared to the previous test.
+ */
+async function checkState(test, lastTest, callback) {
+ let addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id));
+
+ const bls = Ci.nsIBlocklistService;
+
+ await TestUtils.waitForCondition(() =>
+ ADDONS.every(
+ (addon, i) =>
+ addon[test] == (addons[i].blocklistState == bls.STATE_BLOCKED)
+ )
+ ).catch(() => {
+ /* ignore exceptions; the following test will fail anyway. */
+ });
+
+ for (let [i, addon] of ADDONS.entries()) {
+ var blocked =
+ addons[i].blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED;
+ equal(
+ blocked,
+ addon[test],
+ `Blocklist state should match expected for extension ${addon.id}, test ${test}`
+ );
+ }
+}
+
+add_task(async function test() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ for (let addon of ADDONS) {
+ await createAddon(addon);
+ }
+
+ let addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id));
+ for (var i = 0; i < ADDONS.length; i++) {
+ ok(addons[i], `Addon ${i + 1} should have been correctly installed`);
+ }
+
+ await checkState("start");
+});
+
+/**
+ * Load the toolkit based blocks
+ */
+add_task(async function test_pt2() {
+ await AddonTestUtils.loadBlocklistData(
+ do_get_file("../data/"),
+ "test_bug449027_toolkit"
+ );
+
+ await checkState("toolkitBlocks", "start");
+});
+
+/**
+ * Load the application based blocks
+ */
+add_task(async function test_pt3() {
+ await AddonTestUtils.loadBlocklistData(
+ do_get_file("../data/"),
+ "test_bug449027_app"
+ );
+
+ await checkState("appBlocks", "toolkitBlocks");
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_clients.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_clients.js
new file mode 100644
index 0000000000..2ddb4fe514
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_clients.js
@@ -0,0 +1,228 @@
+const { BlocklistPrivate } = ChromeUtils.importESModule(
+ "resource://gre/modules/Blocklist.sys.mjs"
+);
+const { Utils: RemoteSettingsUtils } = ChromeUtils.importESModule(
+ "resource://services-settings/Utils.sys.mjs"
+);
+const { RemoteSettings } = ChromeUtils.importESModule(
+ "resource://services-settings/remote-settings.sys.mjs"
+);
+
+const IS_ANDROID_WITH_BLOCKLIST_V2 =
+ AppConstants.platform == "android" && !AppConstants.NIGHTLY_BUILD;
+let gBlocklistClients;
+
+async function clear_state() {
+ RemoteSettings.enablePreviewMode(undefined);
+
+ for (let { client } of gBlocklistClients) {
+ // Remove last server times.
+ Services.prefs.clearUserPref(client.lastCheckTimePref);
+
+ // Clear local DB.
+ await client.db.clear();
+ }
+}
+
+add_task(async function setup() {
+ AddonTestUtils.createAppInfo(
+ "XPCShell",
+ "xpcshell@tests.mozilla.org",
+ "1",
+ ""
+ );
+
+ // This will initialize the remote settings clients for blocklists.
+ BlocklistPrivate.ExtensionBlocklistRS.ensureInitialized();
+ BlocklistPrivate.GfxBlocklistRS._ensureInitialized();
+
+ // ExtensionBlocklistMLBF is covered by test_blocklist_mlbf_dump.js.
+ gBlocklistClients = [
+ {
+ client: BlocklistPrivate.ExtensionBlocklistRS._client,
+ expectHasDump: IS_ANDROID_WITH_BLOCKLIST_V2,
+ },
+ {
+ client: BlocklistPrivate.GfxBlocklistRS._client,
+ expectHasDump: true,
+ },
+ ];
+
+ await promiseStartupManager();
+});
+
+add_task(
+ async function test_initial_dump_is_loaded_as_synced_when_collection_is_empty() {
+ for (let { client, expectHasDump } of gBlocklistClients) {
+ Assert.equal(
+ await RemoteSettingsUtils.hasLocalDump(
+ client.bucketName,
+ client.collectionName
+ ),
+ expectHasDump,
+ `Expected initial remote settings dump for ${client.collectionName}`
+ );
+ }
+ }
+);
+add_task(clear_state);
+
+add_task(async function test_data_is_filtered_for_target() {
+ const initial = [
+ {
+ guid: "foo",
+ matchName: "foo",
+ versionRange: [
+ {
+ targetApplication: [],
+ maxVersion: "*",
+ minVersion: "0",
+ severity: "1",
+ },
+ ],
+ },
+ ];
+ const noMatchingTarget = [
+ {
+ guid: "foo",
+ matchName: "foo",
+ versionRange: [
+ {
+ targetApplication: [{ guid: "Foo" }],
+ maxVersion: "*",
+ minVersion: "0",
+ severity: "3",
+ },
+ ],
+ },
+ {
+ guid: "foo",
+ matchName: "foo",
+ versionRange: [
+ {
+ targetApplication: [{ guid: "XPCShell", maxVersion: "0.1" }],
+ maxVersion: "*",
+ minVersion: "0",
+ severity: "1",
+ },
+ ],
+ },
+ ];
+ const oneMatch = [
+ {
+ guid: "foo",
+ matchName: "foo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: "XPCShell",
+ },
+ ],
+ },
+ ],
+ },
+ ];
+
+ const records = initial.concat(noMatchingTarget).concat(oneMatch);
+
+ for (let { client } of gBlocklistClients) {
+ // Initialize the collection with some data
+ for (const record of records) {
+ await client.db.create(record);
+ }
+
+ const internalData = await client.db.list();
+ Assert.equal(internalData.length, records.length);
+ let filtered = await client.get({ syncIfEmpty: false });
+ Assert.equal(filtered.length, 2); // only two matches.
+ }
+});
+add_task(clear_state);
+
+add_task(
+ async function test_entries_are_filtered_when_jexl_filter_expression_is_present() {
+ const records = [
+ {
+ guid: "foo",
+ matchName: "foo",
+ willMatch: true,
+ },
+ {
+ guid: "foo",
+ matchName: "foo",
+ willMatch: true,
+ filter_expression: null,
+ },
+ {
+ guid: "foo",
+ matchName: "foo",
+ willMatch: true,
+ filter_expression: "1 == 1",
+ },
+ {
+ guid: "foo",
+ matchName: "foo",
+ willMatch: false,
+ filter_expression: "1 == 2",
+ },
+ {
+ guid: "foo",
+ matchName: "foo",
+ willMatch: true,
+ filter_expression: "1 == 1",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: "some-guid",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ guid: "foo",
+ matchName: "foo",
+ willMatch: false, // jexl prevails over versionRange.
+ filter_expression: "1 == 2",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: "xpcshell@tests.mozilla.org",
+ minVersion: "0",
+ maxVersion: "*",
+ },
+ ],
+ },
+ ],
+ },
+ ];
+ for (let { client } of gBlocklistClients) {
+ for (const record of records) {
+ await client.db.create(record);
+ }
+ const list = await client.get({
+ loadDumpIfNewer: false,
+ syncIfEmpty: false,
+ });
+ equal(list.length, 4);
+ ok(list.every(e => e.willMatch));
+ }
+ }
+);
+add_task(clear_state);
+
+add_task(async function test_bucketname_changes_when_preview_mode_is_enabled() {
+ for (const { client } of gBlocklistClients) {
+ equal(client.bucketName, "blocklists");
+ }
+
+ RemoteSettings.enablePreviewMode(true);
+
+ for (const { client } of gBlocklistClients) {
+ equal(client.bucketName, "blocklists-preview", client.identifier);
+ }
+});
+add_task(clear_state);
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_gfx.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_gfx.js
new file mode 100644
index 0000000000..2b243ec650
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_gfx.js
@@ -0,0 +1,113 @@
+const EVENT_NAME = "blocklist-data-gfxItems";
+
+const SAMPLE_GFX_RECORD = {
+ driverVersionComparator: "LESS_THAN_OR_EQUAL",
+ driverVersion: "8.17.12.5896",
+ vendor: "0x10de",
+ blockID: "g36",
+ feature: "DIRECT3D_9_LAYERS",
+ devices: ["0x0a6c", "geforce"],
+ featureStatus: "BLOCKED_DRIVER_VERSION",
+ last_modified: 9999999999999, // High timestamp to prevent load of dump
+ os: "WINNT 6.1",
+ id: "3f947f16-37c2-4e96-d356-78b26363729b",
+ versionRange: { minVersion: 0, maxVersion: "*" },
+};
+
+add_task(async function test_sends_serialized_data() {
+ const expected =
+ "blockID:g36\tdevices:0x0a6c,geforce\tdriverVersion:8.17.12.5896\t" +
+ "driverVersionComparator:LESS_THAN_OR_EQUAL\tfeature:DIRECT3D_9_LAYERS\t" +
+ "featureStatus:BLOCKED_DRIVER_VERSION\tos:WINNT 6.1\tvendor:0x10de\t" +
+ "versionRange:0,*";
+ let received;
+ const observe = (subject, topic, data) => {
+ received = data;
+ };
+ Services.obs.addObserver(observe, EVENT_NAME);
+ await mockGfxBlocklistItems([SAMPLE_GFX_RECORD]);
+ Services.obs.removeObserver(observe, EVENT_NAME);
+
+ equal(received, expected);
+});
+
+add_task(async function test_parsing_skips_devices_with_comma() {
+ let clonedItem = Cu.cloneInto(SAMPLE_GFX_RECORD, this);
+ clonedItem.devices[0] = "0x2,582";
+ let rv = await mockGfxBlocklistItems([clonedItem]);
+ equal(rv[0].devices.length, 1);
+ equal(rv[0].devices[0], "geforce");
+});
+
+add_task(async function test_empty_values_are_ignored() {
+ let received;
+ const observe = (subject, topic, data) => {
+ received = data;
+ };
+ Services.obs.addObserver(observe, EVENT_NAME);
+ let clonedItem = Cu.cloneInto(SAMPLE_GFX_RECORD, this);
+ clonedItem.os = "";
+ await mockGfxBlocklistItems([clonedItem]);
+ ok(!received.includes("os"), "Shouldn't send empty values");
+ Services.obs.removeObserver(observe, EVENT_NAME);
+});
+
+add_task(async function test_empty_devices_are_ignored() {
+ let received;
+ const observe = (subject, topic, data) => {
+ received = data;
+ };
+ Services.obs.addObserver(observe, EVENT_NAME);
+ let clonedItem = Cu.cloneInto(SAMPLE_GFX_RECORD, this);
+ clonedItem.devices = [];
+ await mockGfxBlocklistItems([clonedItem]);
+ ok(!received.includes("devices"), "Shouldn't send empty values");
+ Services.obs.removeObserver(observe, EVENT_NAME);
+});
+
+add_task(async function test_version_range_default_values() {
+ const kTests = [
+ {
+ input: { minVersion: "13.0b2", maxVersion: "42.0" },
+ output: { minVersion: "13.0b2", maxVersion: "42.0" },
+ },
+ {
+ input: { maxVersion: "2.0" },
+ output: { minVersion: "0", maxVersion: "2.0" },
+ },
+ {
+ input: { minVersion: "1.0" },
+ output: { minVersion: "1.0", maxVersion: "*" },
+ },
+ {
+ input: { minVersion: " " },
+ output: { minVersion: "0", maxVersion: "*" },
+ },
+ {
+ input: {},
+ output: { minVersion: "0", maxVersion: "*" },
+ },
+ ];
+ for (let test of kTests) {
+ let parsedEntries = await mockGfxBlocklistItems([
+ { versionRange: test.input },
+ ]);
+ equal(parsedEntries[0].versionRange.minVersion, test.output.minVersion);
+ equal(parsedEntries[0].versionRange.maxVersion, test.output.maxVersion);
+ }
+});
+
+add_task(async function test_blockid_attribute() {
+ const kTests = [
+ { blockID: "g60", vendor: " 0x10de " },
+ { feature: " DIRECT3D_9_LAYERS " },
+ ];
+ for (let test of kTests) {
+ let [rv] = await mockGfxBlocklistItems([test]);
+ if (test.blockID) {
+ equal(rv.blockID, test.blockID);
+ } else {
+ ok(!rv.hasOwnProperty("blockID"), "not expecting a blockID");
+ }
+ }
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_metadata_filters.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_metadata_filters.js
new file mode 100644
index 0000000000..8f7ecbdf29
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_metadata_filters.js
@@ -0,0 +1,116 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests blocking of extensions by ID, name, creator, homepageURL, updateURL
+// and RegExps for each. See bug 897735.
+
+// useMLBF=true only supports blocking by version+ID, not by other fields.
+enable_blocklist_v2_instead_of_useMLBF();
+
+const BLOCKLIST_DATA = {
+ extensions: [
+ {
+ guid: null,
+ name: "/^Mozilla Corp\\.$/",
+ versionRange: [
+ {
+ severity: "1",
+ targetApplication: [
+ {
+ guid: "xpcshell@tests.mozilla.org",
+ maxVersion: "2.*",
+ minVersion: "1",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ guid: "/block2/",
+ name: "/^Moz/",
+ homepageURL: "/\\.dangerous\\.com/",
+ updateURL: "/\\.dangerous\\.com/",
+ versionRange: [
+ {
+ severity: "3",
+ targetApplication: [
+ {
+ guid: "xpcshell@tests.mozilla.org",
+ maxVersion: "2.*",
+ minVersion: "1",
+ },
+ ],
+ },
+ ],
+ },
+ ],
+};
+
+add_task(async function setup() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+
+ await promiseStartupManager();
+
+ // Should get blocked by name
+ await promiseInstallWebExtension({
+ manifest: {
+ name: "Mozilla Corp.",
+ version: "1.0",
+ browser_specific_settings: { gecko: { id: "block1@tests.mozilla.org" } },
+ },
+ });
+
+ // Should get blocked by all the attributes.
+ await promiseInstallWebExtension({
+ manifest: {
+ name: "Moz-addon",
+ version: "1.0",
+ homepage_url: "https://www.extension.dangerous.com/",
+ browser_specific_settings: {
+ gecko: {
+ id: "block2@tests.mozilla.org",
+ update_url: "https://www.extension.dangerous.com/update.json",
+ },
+ },
+ },
+ });
+
+ // Fails to get blocked because of a different ID even though other
+ // attributes match against a blocklist entry.
+ await promiseInstallWebExtension({
+ manifest: {
+ name: "Moz-addon",
+ version: "1.0",
+ homepage_url: "https://www.extension.dangerous.com/",
+ browser_specific_settings: {
+ gecko: {
+ id: "block3@tests.mozilla.org",
+ update_url: "https://www.extension.dangerous.com/update.json",
+ },
+ },
+ },
+ });
+
+ let [a1, a2, a3] = await AddonManager.getAddonsByIDs([
+ "block1@tests.mozilla.org",
+ "block2@tests.mozilla.org",
+ "block3@tests.mozilla.org",
+ ]);
+ Assert.equal(a1.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ Assert.equal(a2.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ Assert.equal(a3.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+});
+
+add_task(async function test_blocks() {
+ await AddonTestUtils.loadBlocklistRawData(BLOCKLIST_DATA);
+
+ let [a1, a2, a3] = await AddonManager.getAddonsByIDs([
+ "block1@tests.mozilla.org",
+ "block2@tests.mozilla.org",
+ "block3@tests.mozilla.org",
+ ]);
+ Assert.equal(a1.blocklistState, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ Assert.equal(a2.blocklistState, Ci.nsIBlocklistService.STATE_BLOCKED);
+ Assert.equal(a3.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf.js
new file mode 100644
index 0000000000..1f36fc046d
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf.js
@@ -0,0 +1,267 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true);
+
+const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+AddonTestUtils.useRealCertChecks = true;
+
+// A real, signed XPI for use in the test.
+const SIGNED_ADDON_XPI_FILE = do_get_file("../data/webext-implicit-id.xpi");
+const SIGNED_ADDON_ID = "webext_implicit_id@tests.mozilla.org";
+const SIGNED_ADDON_VERSION = "1.0";
+const SIGNED_ADDON_KEY = `${SIGNED_ADDON_ID}:${SIGNED_ADDON_VERSION}`;
+const SIGNED_ADDON_SIGN_TIME = 1459980789000; // notBefore of certificate.
+
+// A real, signed sitepermission XPI for use in the test.
+const SIGNED_SITEPERM_XPI_FILE = do_get_file("webmidi_permission.xpi");
+const SIGNED_SITEPERM_ADDON_ID = "webmidi@test.mozilla.org";
+const SIGNED_SITEPERM_ADDON_VERSION = "1.0.2";
+const SIGNED_SITEPERM_KEY = `${SIGNED_SITEPERM_ADDON_ID}:${SIGNED_SITEPERM_ADDON_VERSION}`;
+const SIGNED_SITEPERM_SIGN_TIME = 1637606460000; // notBefore of certificate.
+
+function mockMLBF({ blocked = [], notblocked = [], generationTime }) {
+ // Mock _fetchMLBF to be able to have a deterministic cascade filter.
+ ExtensionBlocklistMLBF._fetchMLBF = async () => {
+ return {
+ cascadeFilter: {
+ has(blockKey) {
+ if (blocked.includes(blockKey)) {
+ return true;
+ }
+ if (notblocked.includes(blockKey)) {
+ return false;
+ }
+ throw new Error(`Block entry must explicitly be listed: ${blockKey}`);
+ },
+ },
+ generationTime,
+ };
+ };
+}
+
+add_task(async function setup() {
+ await promiseStartupManager();
+ mockMLBF({});
+ await AddonTestUtils.loadBlocklistRawData({
+ extensionsMLBF: [MLBF_RECORD],
+ });
+});
+
+// Checks: Initially unblocked, then blocked, then unblocked again.
+add_task(async function signed_xpi_initially_unblocked() {
+ mockMLBF({
+ blocked: [],
+ notblocked: [SIGNED_ADDON_KEY],
+ generationTime: SIGNED_ADDON_SIGN_TIME + 1,
+ });
+ await ExtensionBlocklistMLBF._onUpdate();
+
+ await promiseInstallFile(SIGNED_ADDON_XPI_FILE);
+
+ let addon = await promiseAddonByID(SIGNED_ADDON_ID);
+ Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+
+ mockMLBF({
+ blocked: [SIGNED_ADDON_KEY],
+ notblocked: [],
+ generationTime: SIGNED_ADDON_SIGN_TIME + 1,
+ });
+ await ExtensionBlocklistMLBF._onUpdate();
+ Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_BLOCKED);
+ Assert.deepEqual(
+ await Blocklist.getAddonBlocklistEntry(addon),
+ {
+ state: Ci.nsIBlocklistService.STATE_BLOCKED,
+ url: "https://addons.mozilla.org/en-US/xpcshell/blocked-addon/webext_implicit_id@tests.mozilla.org/1.0/",
+ },
+ "Blocked addon should have blocked entry"
+ );
+
+ mockMLBF({
+ blocked: [SIGNED_ADDON_KEY],
+ notblocked: [],
+ // MLBF generationTime is older, so "blocked" entry should not apply.
+ generationTime: SIGNED_ADDON_SIGN_TIME - 1,
+ });
+ await ExtensionBlocklistMLBF._onUpdate();
+ Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+
+ await addon.uninstall();
+});
+
+// Checks: Initially blocked on install, then unblocked.
+add_task(async function signed_xpi_blocked_on_install() {
+ mockMLBF({
+ blocked: [SIGNED_ADDON_KEY],
+ notblocked: [],
+ generationTime: SIGNED_ADDON_SIGN_TIME + 1,
+ });
+ await ExtensionBlocklistMLBF._onUpdate();
+
+ await promiseInstallFile(SIGNED_ADDON_XPI_FILE);
+ let addon = await promiseAddonByID(SIGNED_ADDON_ID);
+ Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_BLOCKED);
+ Assert.ok(addon.appDisabled, "Blocked add-on is disabled on install");
+
+ mockMLBF({
+ blocked: [],
+ notblocked: [SIGNED_ADDON_KEY],
+ generationTime: SIGNED_ADDON_SIGN_TIME - 1,
+ });
+ await ExtensionBlocklistMLBF._onUpdate();
+ Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ Assert.ok(!addon.appDisabled, "Re-enabled after unblock");
+
+ await addon.uninstall();
+});
+
+// An unsigned add-on cannot be blocked.
+add_task(async function unsigned_not_blocked() {
+ const UNSIGNED_ADDON_ID = "not-signed@tests.mozilla.org";
+ const UNSIGNED_ADDON_VERSION = "1.0";
+ const UNSIGNED_ADDON_KEY = `${UNSIGNED_ADDON_ID}:${UNSIGNED_ADDON_VERSION}`;
+ mockMLBF({
+ blocked: [UNSIGNED_ADDON_KEY],
+ notblocked: [],
+ generationTime: SIGNED_ADDON_SIGN_TIME + 1,
+ });
+ await ExtensionBlocklistMLBF._onUpdate();
+
+ let unsignedAddonFile = createTempWebExtensionFile({
+ manifest: {
+ version: UNSIGNED_ADDON_VERSION,
+ browser_specific_settings: { gecko: { id: UNSIGNED_ADDON_ID } },
+ },
+ });
+
+ // Unsigned add-ons can generally only be loaded as a temporary install.
+ let [addon] = await Promise.all([
+ AddonManager.installTemporaryAddon(unsignedAddonFile),
+ promiseWebExtensionStartup(UNSIGNED_ADDON_ID),
+ ]);
+ Assert.equal(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
+ Assert.equal(addon.signedDate, null);
+ Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(addon),
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "Unsigned temporary add-on is not blocked"
+ );
+ await addon.uninstall();
+});
+
+// To make sure that unsigned_not_blocked did not trivially pass, we also check
+// that add-ons can actually be blocked when installed as a temporary add-on.
+add_task(async function signed_temporary() {
+ mockMLBF({
+ blocked: [SIGNED_ADDON_KEY],
+ notblocked: [],
+ generationTime: SIGNED_ADDON_SIGN_TIME + 1,
+ });
+ await ExtensionBlocklistMLBF._onUpdate();
+
+ await Assert.rejects(
+ AddonManager.installTemporaryAddon(SIGNED_ADDON_XPI_FILE),
+ /Add-on webext_implicit_id@tests.mozilla.org is not compatible with application version/,
+ "Blocklisted add-on cannot be installed"
+ );
+});
+
+// A privileged add-on cannot be blocked by the MLBF.
+// It can still be blocked by a stash, which is tested in
+// privileged_addon_blocked_by_stash in test_blocklist_mlbf_stashes.js.
+add_task(async function privileged_xpi_not_blocked() {
+ mockMLBF({
+ blocked: ["test@tests.mozilla.org:2.0"],
+ notblocked: [],
+ generationTime: 1546297200000, // 1 jan 2019 = after the cert's notBefore
+ });
+ await ExtensionBlocklistMLBF._onUpdate();
+
+ await promiseInstallFile(
+ do_get_file("../data/signing_checks/privileged.xpi")
+ );
+ let addon = await promiseAddonByID("test@tests.mozilla.org");
+ Assert.equal(addon.signedState, AddonManager.SIGNEDSTATE_PRIVILEGED);
+ Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ await addon.uninstall();
+});
+
+// Langpacks cannot be blocked via the MLBF on Nightly.
+// It can still be blocked by a stash, which is tested in
+// langpack_blocked_by_stash in test_blocklist_mlbf_stashes.js.
+add_task(async function langpack_not_blocked_on_Nightly() {
+ mockMLBF({
+ blocked: ["langpack-klingon@firefox.mozilla.org:1.0"],
+ notblocked: [],
+ generationTime: 1546297200000, // 1 jan 2019 = after the cert's notBefore
+ });
+ await ExtensionBlocklistMLBF._onUpdate();
+
+ await promiseInstallFile(
+ do_get_file("../data/signing_checks/langpack_signed.xpi")
+ );
+ let addon = await promiseAddonByID("langpack-klingon@firefox.mozilla.org");
+ Assert.equal(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED);
+ if (AppConstants.NIGHTLY_BUILD) {
+ // Langpacks built for Nightly are currently signed by releng and not
+ // submitted to AMO, so we have to ignore the blocks of the MLBF.
+ Assert.equal(
+ addon.blocklistState,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "Langpacks cannot be blocked via the MLBF"
+ );
+ } else {
+ // On non-Nightly, langpacks are submitted through AMO so we will enforce
+ // the MLBF blocklist for them.
+ Assert.equal(addon.blocklistState, Ci.nsIBlocklistService.STATE_BLOCKED);
+ }
+ await addon.uninstall();
+});
+
+// Checks: Signed sitepermission addon, initially blocked on install, then unblocked.
+add_task(async function signed_sitepermission_xpi_blocked_on_install() {
+ mockMLBF({
+ blocked: [SIGNED_SITEPERM_KEY],
+ notblocked: [],
+ generationTime: SIGNED_SITEPERM_SIGN_TIME + 1,
+ });
+ await ExtensionBlocklistMLBF._onUpdate();
+
+ await promiseInstallFile(SIGNED_SITEPERM_XPI_FILE);
+ let addon = await promiseAddonByID(SIGNED_SITEPERM_ADDON_ID);
+ // NOTE: if this assertion fails, then SIGNED_SITEPERM_SIGN_TIME has to be
+ // updated accordingly otherwise the addon would not be blocked on install
+ // as this test expects (using the value got from `addon.signedDate.getTime()`)
+ equal(
+ addon.signedDate?.getTime(),
+ SIGNED_SITEPERM_SIGN_TIME,
+ "The addon xpi has the expected signedDate timestamp"
+ );
+ Assert.equal(
+ addon.blocklistState,
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "Got the expected STATE_BLOCKED blocklistState"
+ );
+ Assert.ok(addon.appDisabled, "Blocked add-on is disabled on install");
+
+ mockMLBF({
+ blocked: [],
+ notblocked: [SIGNED_SITEPERM_KEY],
+ generationTime: SIGNED_SITEPERM_SIGN_TIME - 1,
+ });
+ await ExtensionBlocklistMLBF._onUpdate();
+ Assert.equal(
+ addon.blocklistState,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "Got the expected STATE_NOT_BLOCKED blocklistState"
+ );
+ Assert.ok(!addon.appDisabled, "Re-enabled after unblock");
+
+ await addon.uninstall();
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_dump.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_dump.js
new file mode 100644
index 0000000000..3cbd607339
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_dump.js
@@ -0,0 +1,156 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * @fileOverview Verifies that the MLBF dump of the addons blocklist is
+ * correctly registered.
+ */
+
+Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true);
+
+const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF();
+
+// A known blocked version from bug 1626602.
+const blockedAddon = {
+ id: "{6f62927a-e380-401a-8c9e-c485b7d87f0d}",
+ version: "9.2.0",
+ // The following date is the date of the first checked in MLBF. Any MLBF
+ // generated in the future should be generated after this date, to be useful.
+ signedDate: new Date(1588098908496), // 2020-04-28 (dummy date)
+ signedState: AddonManager.SIGNEDSTATE_SIGNED,
+};
+
+// A known add-on that is not blocked, as of writing. It is likely not going
+// to be blocked because it does not have any executable code.
+const nonBlockedAddon = {
+ id: "disable-ctrl-q-and-cmd-q@robwu.nl",
+ version: "1",
+ signedDate: new Date(1482430349000), // 2016-12-22 (actual signing time).
+ signedState: AddonManager.SIGNEDSTATE_SIGNED,
+};
+
+async function sha256(arrayBuffer) {
+ Cu.importGlobalProperties(["crypto"]);
+ let hash = await crypto.subtle.digest("SHA-256", arrayBuffer);
+ const toHex = b => b.toString(16).padStart(2, "0");
+ return Array.from(new Uint8Array(hash), toHex).join("");
+}
+
+// A list of { inputRecord, downloadPromise }:
+// - inputRecord is the record that was used for looking up the MLBF.
+// - downloadPromise is the result of trying to download it.
+const observed = [];
+
+add_task(async function setup() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+ ExtensionBlocklistMLBF.ensureInitialized();
+
+ // Tapping into the internals of ExtensionBlocklistMLBF._fetchMLBF to observe
+ // MLBF request details.
+
+ // Despite being called "download", this does not actually access the network
+ // when there is a valid dump.
+ const originalImpl = ExtensionBlocklistMLBF._client.attachments.download;
+ ExtensionBlocklistMLBF._client.attachments.download = function (record) {
+ let downloadPromise = originalImpl.apply(this, arguments);
+ observed.push({ inputRecord: record, downloadPromise });
+ return downloadPromise;
+ };
+
+ await promiseStartupManager();
+});
+
+async function verifyBlocklistWorksWithDump() {
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(blockedAddon),
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "A add-on that is known to be on the blocklist should be blocked"
+ );
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(nonBlockedAddon),
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "A known non-blocked add-on should not be blocked"
+ );
+}
+
+add_task(async function verify_dump_first_run() {
+ await verifyBlocklistWorksWithDump();
+ Assert.equal(observed.length, 1, "expected number of MLBF download requests");
+
+ const { inputRecord, downloadPromise } = observed.pop();
+
+ Assert.ok(inputRecord, "addons-bloomfilters collection dump exists");
+
+ const downloadResult = await downloadPromise;
+
+ // Verify that the "download" result really originates from the local dump.
+ // "dump_match" means that the record exists in the collection and that an
+ // attachment was found.
+ //
+ // If this fails:
+ // - "dump_fallback" means that the MLBF attachment is out of sync with the
+ // collection data.
+ // - undefined could mean that the implementation of Attachments.sys.mjs changed.
+ Assert.equal(
+ downloadResult._source,
+ "dump_match",
+ "MLBF attachment should match the RemoteSettings collection"
+ );
+
+ Assert.equal(
+ await sha256(downloadResult.buffer),
+ inputRecord.attachment.hash,
+ "The content of the attachment should actually matches the record"
+ );
+});
+
+add_task(async function use_dump_fallback_when_collection_is_out_of_sync() {
+ await AddonTestUtils.loadBlocklistRawData({
+ // last_modified higher than any value in addons-bloomfilters.json.
+ extensionsMLBF: [{ last_modified: Date.now() }],
+ });
+ Assert.equal(observed.length, 1, "Expected new download on update");
+
+ const { inputRecord, downloadPromise } = observed.pop();
+ Assert.equal(inputRecord, null, "No MLBF record found");
+
+ const downloadResult = await downloadPromise;
+ Assert.equal(
+ downloadResult._source,
+ "dump_fallback",
+ "should have used fallback despite the absence of a MLBF record"
+ );
+
+ await verifyBlocklistWorksWithDump();
+ Assert.equal(observed.length, 0, "Blocklist uses cached result");
+});
+
+// Verifies that the dump would supersede local data. This can happen after an
+// application upgrade, where the local database contains outdated records from
+// a previous application version.
+add_task(async function verify_dump_supersedes_old_dump() {
+ // Delete in-memory value; otherwise the cached record from the previous test
+ // task would be re-used and nothing would be downloaded.
+ delete ExtensionBlocklistMLBF._mlbfData;
+
+ await AddonTestUtils.loadBlocklistRawData({
+ // last_modified lower than any value in addons-bloomfilters.json.
+ extensionsMLBF: [{ last_modified: 1 }],
+ });
+ Assert.equal(observed.length, 1, "Expected new download on update");
+
+ const { inputRecord, downloadPromise } = observed.pop();
+ Assert.ok(inputRecord, "should have read from addons-bloomfilters dump");
+
+ const downloadResult = await downloadPromise;
+ Assert.equal(
+ downloadResult._source,
+ "dump_match",
+ "Should have replaced outdated collection records with dump"
+ );
+
+ await verifyBlocklistWorksWithDump();
+ Assert.equal(observed.length, 0, "Blocklist uses cached result");
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_fetch.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_fetch.js
new file mode 100644
index 0000000000..92bf61dbde
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_fetch.js
@@ -0,0 +1,231 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * @fileOverview Tests the MLBF and RemoteSettings synchronization logic.
+ */
+
+Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true);
+
+const { Downloader } = ChromeUtils.importESModule(
+ "resource://services-settings/Attachments.sys.mjs"
+);
+
+const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF();
+
+// This test needs to interact with the RemoteSettings client.
+ExtensionBlocklistMLBF.ensureInitialized();
+
+add_task(async function fetch_invalid_mlbf_record() {
+ let invalidRecord = {
+ attachment: { size: 1, hash: "definitely not valid" },
+ generation_time: 1,
+ };
+
+ // _fetchMLBF(invalidRecord) may succeed if there is a MLBF dump packaged with
+ // the application. This test intentionally hides the actual path to get
+ // deterministic results. To check whether the dump is correctly registered,
+ // run test_blocklist_mlbf_dump.js
+
+ // Forget about the packaged attachment.
+ Downloader._RESOURCE_BASE_URL = "invalid://bogus";
+ // NetworkError is expected here. The JSON.parse error could be triggered via
+ // _baseAttachmentsURL < downloadAsBytes < download < download < _fetchMLBF if
+ // the request to services.settings.server ("data:,#remote-settings-dummy/v1")
+ // is fulfilled (but with invalid JSON). That request is not expected to be
+ // fulfilled in the first place, but that is not a concern of this test.
+ // This test passes if _fetchMLBF() rejects when given an invalid record.
+ await Assert.rejects(
+ ExtensionBlocklistMLBF._fetchMLBF(invalidRecord),
+ /NetworkError|SyntaxError: JSON\.parse/,
+ "record not found when there is no packaged MLBF"
+ );
+});
+
+// Other tests can mock _testMLBF, so let's verify that it works as expected.
+add_task(async function fetch_valid_mlbf() {
+ await ExtensionBlocklistMLBF._client.db.saveAttachment(
+ ExtensionBlocklistMLBF.RS_ATTACHMENT_ID,
+ { record: MLBF_RECORD, blob: await load_mlbf_record_as_blob() }
+ );
+
+ const result = await ExtensionBlocklistMLBF._fetchMLBF(MLBF_RECORD);
+ Assert.equal(result.cascadeHash, MLBF_RECORD.attachment.hash, "hash OK");
+ Assert.equal(result.generationTime, MLBF_RECORD.generation_time, "time OK");
+ Assert.ok(result.cascadeFilter.has("@blocked:1"), "item blocked");
+ Assert.ok(!result.cascadeFilter.has("@unblocked:2"), "item not blocked");
+
+ const result2 = await ExtensionBlocklistMLBF._fetchMLBF({
+ attachment: { size: 1, hash: "invalid" },
+ generation_time: Date.now(),
+ });
+ Assert.equal(
+ result2.cascadeHash,
+ MLBF_RECORD.attachment.hash,
+ "The cached MLBF should be used when the attachment is invalid"
+ );
+
+ // The attachment is kept in the database for use by the next test task.
+});
+
+// Test that results of the public API are consistent with the MLBF file.
+add_task(async function public_api_uses_mlbf() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+ await promiseStartupManager();
+
+ const blockedAddon = {
+ id: "@blocked",
+ version: "1",
+ signedDate: new Date(0), // a date in the past, before MLBF's generationTime.
+ signedState: AddonManager.SIGNEDSTATE_SIGNED,
+ };
+ const nonBlockedAddon = {
+ id: "@unblocked",
+ version: "2",
+ signedDate: new Date(0), // a date in the past, before MLBF's generationTime.
+ signedState: AddonManager.SIGNEDSTATE_SIGNED,
+ };
+
+ await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [MLBF_RECORD] });
+
+ Assert.deepEqual(
+ await Blocklist.getAddonBlocklistEntry(blockedAddon),
+ {
+ state: Ci.nsIBlocklistService.STATE_BLOCKED,
+ url: "https://addons.mozilla.org/en-US/xpcshell/blocked-addon/@blocked/1/",
+ },
+ "Blocked addon should have blocked entry"
+ );
+
+ Assert.deepEqual(
+ await Blocklist.getAddonBlocklistEntry(nonBlockedAddon),
+ null,
+ "Non-blocked addon should not be blocked"
+ );
+
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(blockedAddon),
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "Blocked entry should have blocked state"
+ );
+
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(nonBlockedAddon),
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "Non-blocked entry should have unblocked state"
+ );
+
+ // Note: Blocklist collection and attachment carries over to the next test.
+});
+
+// Verifies that the metadata (time of validity) of an updated MLBF record is
+// correctly used, even if the MLBF itself has not changed.
+add_task(async function fetch_updated_mlbf_same_hash() {
+ const recordUpdate = {
+ ...MLBF_RECORD,
+ generation_time: MLBF_RECORD.generation_time + 1,
+ };
+ const blockedAddonUpdate = {
+ id: "@blocked",
+ version: "1",
+ signedDate: new Date(recordUpdate.generation_time),
+ signedState: AddonManager.SIGNEDSTATE_SIGNED,
+ };
+
+ // The blocklist already includes "@blocked:1", but the last specified
+ // generation time is MLBF_RECORD.generation_time. So the addon cannot be
+ // blocked, because the block decision could be a false positive.
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(blockedAddonUpdate),
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "Add-on not blocked before blocklist update"
+ );
+
+ await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [recordUpdate] });
+ // The MLBF is now known to apply to |blockedAddonUpdate|.
+
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(blockedAddonUpdate),
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "Add-on blocked after update"
+ );
+
+ // Note: Blocklist collection and attachment carries over to the next test.
+});
+
+// Checks the remaining cases of database corruption that haven't been handled
+// before.
+add_task(async function handle_database_corruption() {
+ const blockedAddon = {
+ id: "@blocked",
+ version: "1",
+ signedDate: new Date(0), // a date in the past, before MLBF's generationTime.
+ signedState: AddonManager.SIGNEDSTATE_SIGNED,
+ };
+ async function checkBlocklistWorks() {
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(blockedAddon),
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "Add-on should be blocked by the blocklist"
+ );
+ }
+
+ let fetchCount = 0;
+ const originalFetchMLBF = ExtensionBlocklistMLBF._fetchMLBF;
+ ExtensionBlocklistMLBF._fetchMLBF = function () {
+ ++fetchCount;
+ return originalFetchMLBF.apply(this, arguments);
+ };
+
+ // In the fetch_invalid_mlbf_record we checked that a cached / packaged MLBF
+ // attachment is used as a fallback when the record is invalid. Here we also
+ // check that there is a fallback when there is no record at all.
+
+ // Include a dummy record in the list, to prevent RemoteSettings from
+ // importing a JSON dump with unexpected records.
+ await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [{}] });
+ Assert.equal(fetchCount, 1, "MLBF read once despite bad record");
+ // When the collection is empty, the last known MLBF should be used anyway.
+ await checkBlocklistWorks();
+ Assert.equal(fetchCount, 1, "MLBF not read again by blocklist query");
+
+ // Now we also remove the cached file...
+ await ExtensionBlocklistMLBF._client.db.saveAttachment(
+ ExtensionBlocklistMLBF.RS_ATTACHMENT_ID,
+ null
+ );
+ Assert.equal(fetchCount, 1, "MLBF not read again after attachment deletion");
+ // Deleting the file shouldn't cause issues because the MLBF is loaded once
+ // and then kept in memory.
+ await checkBlocklistWorks();
+ Assert.equal(fetchCount, 1, "MLBF not read again by blocklist query 2");
+
+ // Force an update while we don't have any blocklist data nor cache.
+ await ExtensionBlocklistMLBF._onUpdate();
+ Assert.equal(fetchCount, 2, "MLBF read again at forced update");
+ // As a fallback, continue to use the in-memory version of the blocklist.
+ await checkBlocklistWorks();
+ Assert.equal(fetchCount, 2, "MLBF not read again by blocklist query 3");
+
+ // Memory gone, e.g. after a browser restart.
+ delete ExtensionBlocklistMLBF._mlbfData;
+ delete ExtensionBlocklistMLBF._stashes;
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(blockedAddon),
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "Blocklist can't work if all blocklist data is gone"
+ );
+ Assert.equal(fetchCount, 3, "MLBF read again after restart/cleared cache");
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(blockedAddon),
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "Blocklist can still not work if all blocklist data is gone"
+ );
+ // Ideally, the client packages a dump. But if the client did not package the
+ // dump, then it should not be trying to read the data over and over again.
+ Assert.equal(fetchCount, 3, "MLBF not read again despite absence of MLBF");
+
+ ExtensionBlocklistMLBF._fetchMLBF = originalFetchMLBF;
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_stashes.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_stashes.js
new file mode 100644
index 0000000000..e129efc793
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_stashes.js
@@ -0,0 +1,219 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true);
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+
+const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF();
+const MLBF_LOAD_ATTEMPTS = [];
+ExtensionBlocklistMLBF._fetchMLBF = async record => {
+ MLBF_LOAD_ATTEMPTS.push(record);
+ return {
+ generationTime: 0,
+ cascadeFilter: {
+ has(blockKey) {
+ if (blockKey === "@onlyblockedbymlbf:1") {
+ return true;
+ }
+ throw new Error("bloom filter should not be used in this test");
+ },
+ },
+ };
+};
+
+async function checkBlockState(addonId, version, expectBlocked) {
+ let addon = {
+ id: addonId,
+ version,
+ // Note: signedDate is missing, so the MLBF does not apply
+ // and we will effectively only test stashing.
+ };
+ let state = await Blocklist.getAddonBlocklistState(addon);
+ if (expectBlocked) {
+ Assert.equal(state, Ci.nsIBlocklistService.STATE_BLOCKED);
+ } else {
+ Assert.equal(state, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ }
+}
+
+add_task(async function setup() {
+ await promiseStartupManager();
+});
+
+// Tests that add-ons can be blocked / unblocked via the stash.
+add_task(async function basic_stash() {
+ await AddonTestUtils.loadBlocklistRawData({
+ extensionsMLBF: [
+ {
+ stash_time: 0,
+ stash: {
+ blocked: ["@blocked:1"],
+ unblocked: ["@notblocked:2"],
+ },
+ },
+ ],
+ });
+ await checkBlockState("@blocked", "1", true);
+ await checkBlockState("@notblocked", "2", false);
+ // Not in stash (but unsigned, so shouldn't reach MLBF):
+ await checkBlockState("@blocked", "2", false);
+
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState({
+ id: "@onlyblockedbymlbf",
+ version: "1",
+ signedDate: new Date(0), // = the MLBF's generationTime.
+ signedState: AddonManager.SIGNEDSTATE_SIGNED,
+ }),
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "falls through to MLBF if entry is not found in stash"
+ );
+
+ Assert.deepEqual(MLBF_LOAD_ATTEMPTS, [null], "MLBF attachment not found");
+});
+
+// To complement the privileged_xpi_not_blocked in test_blocklist_mlbf.js,
+// verify that privileged add-ons can still be blocked through stashes.
+add_task(async function privileged_addon_blocked_by_stash() {
+ const system_addon = {
+ id: "@blocked",
+ version: "1",
+ signedDate: new Date(0), // = the MLBF's generationTime.
+ signedState: AddonManager.SIGNEDSTATE_PRIVILEGED,
+ };
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(system_addon),
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "Privileged add-ons can still be blocked by a stash"
+ );
+
+ system_addon.signedState = AddonManager.SIGNEDSTATE_SYSTEM;
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(system_addon),
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "Privileged system add-ons can still be blocked by a stash"
+ );
+
+ // For comparison, when an add-on is only blocked by a MLBF, the block
+ // decision is ignored.
+ system_addon.id = "@onlyblockedbymlbf";
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(system_addon),
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "Privileged add-ons cannot be blocked via a MLBF"
+ );
+ // (note that we haven't checked that SIGNEDSTATE_PRIVILEGED is not blocked
+ // via the MLBF, but that is already covered by test_blocklist_mlbf.js ).
+});
+
+// To complement langpack_not_blocked_on_Nightly in test_blocklist_mlbf.js,
+// verify that langpacks can still be blocked through stashes.
+add_task(async function langpack_blocked_by_stash() {
+ const langpack_addon = {
+ id: "@blocked",
+ type: "locale",
+ version: "1",
+ signedDate: new Date(0), // = the MLBF's generationTime.
+ signedState: AddonManager.SIGNEDSTATE_SIGNED,
+ };
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(langpack_addon),
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "Langpack add-ons can still be blocked by a stash"
+ );
+
+ // For comparison, when an add-on is only blocked by a MLBF, the block
+ // decision is ignored on Nightly (but blocked on non-Nightly).
+ langpack_addon.id = "@onlyblockedbymlbf";
+ if (AppConstants.NIGHTLY_BUILD) {
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(langpack_addon),
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ "Langpack add-ons cannot be blocked via a MLBF on Nightly"
+ );
+ } else {
+ Assert.equal(
+ await Blocklist.getAddonBlocklistState(langpack_addon),
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "Langpack add-ons can be blocked via a MLBF on non-Nightly"
+ );
+ }
+});
+
+// Tests that invalid stash entries are ignored.
+add_task(async function invalid_stashes() {
+ await AddonTestUtils.loadBlocklistRawData({
+ extensionsMLBF: [
+ {},
+ { stash: null },
+ { stash: 1 },
+ { stash: {} },
+ { stash: { blocked: ["@broken:1", "@okid:1"] } },
+ { stash: { unblocked: ["@broken:2"] } },
+ // The only correct entry:
+ { stash: { blocked: ["@okid:2"], unblocked: ["@okid:1"] } },
+ { stash: { blocked: ["@broken:1", "@okid:1"] } },
+ { stash: { unblocked: ["@broken:2", "@okid:2"] } },
+ ],
+ });
+ // The valid stash entry should be applied:
+ await checkBlockState("@okid", "1", false);
+ await checkBlockState("@okid", "2", true);
+ // Entries from invalid stashes should be ignored:
+ await checkBlockState("@broken", "1", false);
+ await checkBlockState("@broken", "2", false);
+});
+
+// Blocklist stashes should be processed in the reverse chronological order.
+add_task(async function stash_time_order() {
+ await AddonTestUtils.loadBlocklistRawData({
+ extensionsMLBF: [
+ // "@a:1" and "@a:2" are blocked at time 1, but unblocked later.
+ { stash_time: 2, stash: { blocked: [], unblocked: ["@a:1"] } },
+ { stash_time: 1, stash: { blocked: ["@a:1", "@a:2"], unblocked: [] } },
+ { stash_time: 3, stash: { blocked: [], unblocked: ["@a:2"] } },
+
+ // "@b:1" and "@b:2" are unblocked at time 4, but blocked later.
+ { stash_time: 5, stash: { blocked: ["@b:1"], unblocked: [] } },
+ { stash_time: 4, stash: { blocked: [], unblocked: ["@b:1", "@b:2"] } },
+ { stash_time: 6, stash: { blocked: ["@b:2"], unblocked: [] } },
+ ],
+ });
+ await checkBlockState("@a", "1", false);
+ await checkBlockState("@a", "2", false);
+
+ await checkBlockState("@b", "1", true);
+ await checkBlockState("@b", "2", true);
+});
+
+// Attachments with unsupported attachment_type should be ignored.
+add_task(async function mlbf_bloomfilter_full_ignored() {
+ MLBF_LOAD_ATTEMPTS.length = 0;
+
+ await AddonTestUtils.loadBlocklistRawData({
+ extensionsMLBF: [{ attachment_type: "bloomfilter-full", attachment: {} }],
+ });
+
+ // Only bloomfilter-base records should be used.
+ // Since there are no such records, we shouldn't find anything.
+ Assert.deepEqual(MLBF_LOAD_ATTEMPTS, [null], "no matching MLBFs found");
+});
+
+// Tests that the most recent MLBF is downloaded.
+add_task(async function mlbf_generation_time_recent() {
+ MLBF_LOAD_ATTEMPTS.length = 0;
+ const records = [
+ { attachment_type: "bloomfilter-base", attachment: {}, generation_time: 2 },
+ { attachment_type: "bloomfilter-base", attachment: {}, generation_time: 3 },
+ { attachment_type: "bloomfilter-base", attachment: {}, generation_time: 1 },
+ ];
+ await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: records });
+ Assert.equal(
+ MLBF_LOAD_ATTEMPTS[0].generation_time,
+ 3,
+ "expected to load most recent MLBF"
+ );
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_telemetry.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_telemetry.js
new file mode 100644
index 0000000000..6ad8e9c2ac
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_telemetry.js
@@ -0,0 +1,188 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true);
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+
+const { Downloader } = ChromeUtils.importESModule(
+ "resource://services-settings/Attachments.sys.mjs"
+);
+
+const { TelemetryController } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryController.sys.mjs"
+);
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+const OLDEST_STASH = { stash: { blocked: [], unblocked: [] }, stash_time: 2e6 };
+const NEWEST_STASH = { stash: { blocked: [], unblocked: [] }, stash_time: 5e6 };
+const RECORDS_WITH_STASHES_AND_MLBF = [MLBF_RECORD, OLDEST_STASH, NEWEST_STASH];
+
+const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF();
+
+function assertTelemetryScalars(expectedScalars) {
+ // On Android, we only report to the Glean system telemetry system.
+ if (IS_ANDROID_BUILD) {
+ info(
+ `Skip assertions on collected samples for ${expectedScalars} on android builds`
+ );
+ return;
+ }
+ let scalars = TelemetryTestUtils.getProcessScalars("parent");
+ for (const scalarName of Object.keys(expectedScalars || {})) {
+ equal(
+ scalars[scalarName],
+ expectedScalars[scalarName],
+ `Got the expected value for ${scalarName} scalar`
+ );
+ }
+}
+
+function toUTC(time) {
+ return new Date(time).toUTCString();
+}
+
+add_task(async function setup() {
+ if (!IS_ANDROID_BUILD) {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+ // FOG needs to be initialized in order for data to flow.
+ Services.fog.initializeFOG();
+ }
+ await TelemetryController.testSetup();
+ await promiseStartupManager();
+
+ // Disable the packaged record and attachment to make sure that the test
+ // will not fall back to the packaged attachments.
+ Downloader._RESOURCE_BASE_URL = "invalid://bogus";
+});
+
+add_task(async function test_initialization() {
+ resetBlocklistTelemetry();
+ ExtensionBlocklistMLBF.ensureInitialized();
+
+ Assert.equal(undefined, testGetValue(Glean.blocklist.mlbfSource));
+ Assert.equal(undefined, testGetValue(Glean.blocklist.mlbfGenerationTime));
+ Assert.equal(undefined, testGetValue(Glean.blocklist.mlbfStashTimeOldest));
+ Assert.equal(undefined, testGetValue(Glean.blocklist.mlbfStashTimeNewest));
+
+ assertTelemetryScalars({
+ // In other parts of this test, this value is not checked any more.
+ // test_blocklist_telemetry.js already checks lastModified_rs_addons_mlbf.
+ "blocklist.lastModified_rs_addons_mlbf": undefined,
+ "blocklist.mlbf_source": undefined,
+ "blocklist.mlbf_generation_time": undefined,
+ "blocklist.mlbf_stash_time_oldest": undefined,
+ "blocklist.mlbf_stash_time_newest": undefined,
+ });
+});
+
+// Test what happens if there is no blocklist data at all.
+add_task(async function test_without_mlbf() {
+ resetBlocklistTelemetry();
+ // Add one (invalid) value to the blocklist, to prevent the RemoteSettings
+ // client from importing the JSON dump (which could potentially cause the
+ // test to fail due to the unexpected imported records).
+ await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [{}] });
+ Assert.equal("unknown", testGetValue(Glean.blocklist.mlbfSource));
+
+ Assert.equal(0, testGetValue(Glean.blocklist.mlbfGenerationTime).getTime());
+ Assert.equal(0, testGetValue(Glean.blocklist.mlbfStashTimeOldest).getTime());
+ Assert.equal(0, testGetValue(Glean.blocklist.mlbfStashTimeNewest).getTime());
+
+ assertTelemetryScalars({
+ "blocklist.mlbf_source": "unknown",
+ "blocklist.mlbf_generation_time": "Missing Date",
+ "blocklist.mlbf_stash_time_oldest": "Missing Date",
+ "blocklist.mlbf_stash_time_newest": "Missing Date",
+ });
+});
+
+// Test the telemetry that would be recorded in the common case.
+add_task(async function test_common_good_case_with_stashes() {
+ resetBlocklistTelemetry();
+ // The exact content of the attachment does not matter in this test, as long
+ // as the data is valid.
+ await ExtensionBlocklistMLBF._client.db.saveAttachment(
+ ExtensionBlocklistMLBF.RS_ATTACHMENT_ID,
+ { record: MLBF_RECORD, blob: await load_mlbf_record_as_blob() }
+ );
+ await AddonTestUtils.loadBlocklistRawData({
+ extensionsMLBF: RECORDS_WITH_STASHES_AND_MLBF,
+ });
+ Assert.equal("cache_match", testGetValue(Glean.blocklist.mlbfSource));
+ Assert.equal(
+ MLBF_RECORD.generation_time,
+ testGetValue(Glean.blocklist.mlbfGenerationTime).getTime()
+ );
+ Assert.equal(
+ OLDEST_STASH.stash_time,
+ testGetValue(Glean.blocklist.mlbfStashTimeOldest).getTime()
+ );
+ Assert.equal(
+ NEWEST_STASH.stash_time,
+ testGetValue(Glean.blocklist.mlbfStashTimeNewest).getTime()
+ );
+ assertTelemetryScalars({
+ "blocklist.mlbf_source": "cache_match",
+ "blocklist.mlbf_generation_time": toUTC(MLBF_RECORD.generation_time),
+ "blocklist.mlbf_stash_time_oldest": toUTC(OLDEST_STASH.stash_time),
+ "blocklist.mlbf_stash_time_newest": toUTC(NEWEST_STASH.stash_time),
+ });
+
+ // The records and cached attachment carries over to the next tests.
+});
+
+// Test what happens when there are no stashes in the collection itself.
+add_task(async function test_without_stashes() {
+ resetBlocklistTelemetry();
+ await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [MLBF_RECORD] });
+
+ Assert.equal("cache_match", testGetValue(Glean.blocklist.mlbfSource));
+ Assert.equal(
+ MLBF_RECORD.generation_time,
+ testGetValue(Glean.blocklist.mlbfGenerationTime).getTime()
+ );
+
+ Assert.equal(0, testGetValue(Glean.blocklist.mlbfStashTimeOldest).getTime());
+ Assert.equal(0, testGetValue(Glean.blocklist.mlbfStashTimeNewest).getTime());
+
+ assertTelemetryScalars({
+ "blocklist.mlbf_source": "cache_match",
+ "blocklist.mlbf_generation_time": toUTC(MLBF_RECORD.generation_time),
+ "blocklist.mlbf_stash_time_oldest": "Missing Date",
+ "blocklist.mlbf_stash_time_newest": "Missing Date",
+ });
+});
+
+// Test what happens when the collection was inadvertently emptied,
+// but still with a cached mlbf from before.
+add_task(async function test_without_collection_but_cache() {
+ resetBlocklistTelemetry();
+ await AddonTestUtils.loadBlocklistRawData({
+ // Insert a dummy record with a value of last_modified which is higher than
+ // any value of last_modified in addons-bloomfilters.json, to prevent the
+ // blocklist implementation from automatically falling back to the packaged
+ // JSON dump.
+ extensionsMLBF: [{ last_modified: Date.now() }],
+ });
+ Assert.equal("cache_fallback", testGetValue(Glean.blocklist.mlbfSource));
+ Assert.equal(
+ MLBF_RECORD.generation_time,
+ testGetValue(Glean.blocklist.mlbfGenerationTime).getTime()
+ );
+
+ Assert.equal(0, testGetValue(Glean.blocklist.mlbfStashTimeOldest).getTime());
+ Assert.equal(0, testGetValue(Glean.blocklist.mlbfStashTimeNewest).getTime());
+
+ assertTelemetryScalars({
+ "blocklist.mlbf_source": "cache_fallback",
+ "blocklist.mlbf_generation_time": toUTC(MLBF_RECORD.generation_time),
+ "blocklist.mlbf_stash_time_oldest": "Missing Date",
+ "blocklist.mlbf_stash_time_newest": "Missing Date",
+ });
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_update.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_update.js
new file mode 100644
index 0000000000..b98d6e345d
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_mlbf_update.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * @fileOverview Checks that the MLBF updating logic works reasonably.
+ */
+
+Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true);
+const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF();
+
+// This test needs to interact with the RemoteSettings client.
+ExtensionBlocklistMLBF.ensureInitialized();
+
+// Multiple internal calls to update should be coalesced and end up with the
+// MLBF attachment from the last update call.
+add_task(async function collapse_multiple_pending_update_requests() {
+ const observed = [];
+
+ // The first step of starting an update is to read from the RemoteSettings
+ // collection. When a non-forced update is requested while another update is
+ // pending, the non-forced update should return/await the previous call
+ // instead of starting a new read/fetch from the RemoteSettings collection.
+ // Add a spy to the RemoteSettings client, so we can verify that the number
+ // of RemoteSettings accesses matches with what we expect.
+ const originalClientGet = ExtensionBlocklistMLBF._client.get;
+ const spyClientGet = (tag, returnValue) => {
+ ExtensionBlocklistMLBF._client.get = async function () {
+ // Record the method call.
+ observed.push(tag);
+ // Clone a valid record and tag it so we can identify it below.
+ let dummyRecord = JSON.parse(JSON.stringify(MLBF_RECORD));
+ dummyRecord.tagged = tag;
+ return [dummyRecord];
+ };
+ };
+
+ // Another significant part of updating is fetching the MLBF attachment.
+ // Add a spy too, so we can check which attachment is being requested.
+ const originalFetchMLBF = ExtensionBlocklistMLBF._fetchMLBF;
+ ExtensionBlocklistMLBF._fetchMLBF = async function (record) {
+ observed.push(`fetchMLBF:${record.tagged}`);
+ throw new Error(`Deliberately ignoring call to MLBF:${record.tagged}`);
+ };
+
+ spyClientGet("initial"); // Very first call = read RS.
+ let update1 = ExtensionBlocklistMLBF._updateMLBF(false);
+ spyClientGet("unexpected update2"); // Non-forced update = reuse update1.
+ let update2 = ExtensionBlocklistMLBF._updateMLBF(false);
+ spyClientGet("forced1"); // forceUpdate=true = supersede previous update.
+ let forcedUpdate1 = ExtensionBlocklistMLBF._updateMLBF(true);
+ spyClientGet("forced2"); // forceUpdate=true = supersede previous update.
+ let forcedUpdate2 = ExtensionBlocklistMLBF._updateMLBF(true);
+
+ let res = await Promise.all([update1, update2, forcedUpdate1, forcedUpdate2]);
+
+ Assert.equal(observed.length, 4, "expected number of observed events");
+ Assert.equal(observed[0], "initial", "First update should request records");
+ Assert.equal(observed[1], "forced1", "Forced update supersedes initial");
+ Assert.equal(observed[2], "forced2", "Forced update supersedes forced1");
+ // We call the _updateMLBF methods immediately after each other. Every update
+ // request starts with an asynchronous operation (looking up the RS records),
+ // so the implementation should return early for all update requests except
+ // for the last one. So we should only observe a fetch for the last request.
+ Assert.equal(observed[3], "fetchMLBF:forced2", "expected fetch result");
+
+ // All update requests should end up with the same result.
+ Assert.equal(res[0], res[1], "update1 == update2");
+ Assert.equal(res[1], res[2], "update2 == forcedUpdate1");
+ Assert.equal(res[2], res[3], "forcedUpdate1 == forcedUpdate2");
+
+ ExtensionBlocklistMLBF._client.get = originalClientGet;
+ ExtensionBlocklistMLBF._fetchMLBF = originalFetchMLBF;
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_osabi.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_osabi.js
new file mode 100644
index 0000000000..243225c6e0
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_osabi.js
@@ -0,0 +1,286 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// useMLBF=true only supports blocking by version+ID, not by OS/ABI.
+enable_blocklist_v2_instead_of_useMLBF();
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+const ADDONS = [
+ {
+ id: "test_bug393285_1@tests.mozilla.org",
+ name: "extension 1",
+ version: "1.0",
+
+ // No info in blocklist, shouldn't be blocked
+ notBlocklisted: [
+ ["1", "1.9"],
+ [null, null],
+ ],
+ },
+ {
+ id: "test_bug393285_2@tests.mozilla.org",
+ name: "extension 2",
+ version: "1.0",
+
+ // Should always be blocked
+ blocklisted: [
+ ["1", "1.9"],
+ [null, null],
+ ],
+ },
+ {
+ id: "test_bug393285_3a@tests.mozilla.org",
+ name: "extension 3a",
+ version: "1.0",
+
+ // Only version 1 should be blocked
+ blocklisted: [
+ ["1", "1.9"],
+ [null, null],
+ ],
+ },
+ {
+ id: "test_bug393285_3b@tests.mozilla.org",
+ name: "extension 3b",
+ version: "2.0",
+
+ // Only version 1 should be blocked
+ notBlocklisted: [["1", "1.9"]],
+ },
+ {
+ id: "test_bug393285_4@tests.mozilla.org",
+ name: "extension 4",
+ version: "1.0",
+
+ // Should be blocked for app version 1
+ blocklisted: [
+ ["1", "1.9"],
+ [null, null],
+ ],
+ notBlocklisted: [["2", "1.9"]],
+ },
+ {
+ id: "test_bug393285_5@tests.mozilla.org",
+ name: "extension 5",
+ version: "1.0",
+
+ // Not blocklisted because we are a different OS
+ notBlocklisted: [["2", "1.9"]],
+ },
+ {
+ id: "test_bug393285_6@tests.mozilla.org",
+ name: "extension 6",
+ version: "1.0",
+
+ // Blocklisted based on OS
+ blocklisted: [["2", "1.9"]],
+ },
+ {
+ id: "test_bug393285_7@tests.mozilla.org",
+ name: "extension 7",
+ version: "1.0",
+
+ // Blocklisted based on OS
+ blocklisted: [["2", "1.9"]],
+ },
+ {
+ id: "test_bug393285_8@tests.mozilla.org",
+ name: "extension 8",
+ version: "1.0",
+
+ // Not blocklisted because we are a different ABI
+ notBlocklisted: [["2", "1.9"]],
+ },
+ {
+ id: "test_bug393285_9@tests.mozilla.org",
+ name: "extension 9",
+ version: "1.0",
+
+ // Blocklisted based on ABI
+ blocklisted: [["2", "1.9"]],
+ },
+ {
+ id: "test_bug393285_10@tests.mozilla.org",
+ name: "extension 10",
+ version: "1.0",
+
+ // Blocklisted based on ABI
+ blocklisted: [["2", "1.9"]],
+ },
+ {
+ id: "test_bug393285_11@tests.mozilla.org",
+ name: "extension 11",
+ version: "1.0",
+
+ // Doesn't match both os and abi so not blocked
+ notBlocklisted: [["2", "1.9"]],
+ },
+ {
+ id: "test_bug393285_12@tests.mozilla.org",
+ name: "extension 12",
+ version: "1.0",
+
+ // Doesn't match both os and abi so not blocked
+ notBlocklisted: [["2", "1.9"]],
+ },
+ {
+ id: "test_bug393285_13@tests.mozilla.org",
+ name: "extension 13",
+ version: "1.0",
+
+ // Doesn't match both os and abi so not blocked
+ notBlocklisted: [["2", "1.9"]],
+ },
+ {
+ id: "test_bug393285_14@tests.mozilla.org",
+ name: "extension 14",
+ version: "1.0",
+
+ // Matches both os and abi so blocked
+ blocklisted: [["2", "1.9"]],
+ },
+];
+
+const ADDON_IDS = ADDONS.map(a => a.id);
+
+const BLOCKLIST_DATA = [
+ {
+ guid: "test_bug393285_2@tests.mozilla.org",
+ versionRange: [],
+ },
+ {
+ guid: "test_bug393285_3a@tests.mozilla.org",
+ versionRange: [{ maxVersion: "1.0", minVersion: "1.0" }],
+ },
+ {
+ guid: "test_bug393285_3b@tests.mozilla.org",
+ versionRange: [{ maxVersion: "1.0", minVersion: "1.0" }],
+ },
+ {
+ guid: "test_bug393285_4@tests.mozilla.org",
+ versionRange: [
+ {
+ maxVersion: "1.0",
+ minVersion: "1.0",
+ targetApplication: [
+ {
+ guid: "xpcshell@tests.mozilla.org",
+ maxVersion: "1.0",
+ minVersion: "1.0",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ guid: "test_bug393285_5@tests.mozilla.org",
+ os: "Darwin",
+ versionRange: [],
+ },
+ {
+ guid: "test_bug393285_6@tests.mozilla.org",
+ os: "XPCShell",
+ versionRange: [],
+ },
+ {
+ guid: "test_bug393285_7@tests.mozilla.org",
+ os: "Darwin,XPCShell,WINNT",
+ versionRange: [],
+ },
+ {
+ guid: "test_bug393285_8@tests.mozilla.org",
+ xpcomabi: "x86-msvc",
+ versionRange: [],
+ },
+ {
+ guid: "test_bug393285_9@tests.mozilla.org",
+ xpcomabi: "noarch-spidermonkey",
+ versionRange: [],
+ },
+ {
+ guid: "test_bug393285_10@tests.mozilla.org",
+ xpcomabi: "ppc-gcc3,noarch-spidermonkey,x86-msvc",
+ versionRange: [],
+ },
+ {
+ guid: "test_bug393285_11@tests.mozilla.org",
+ os: "Darwin",
+ xpcomabi: "ppc-gcc3,x86-msvc",
+ versionRange: [],
+ },
+ {
+ guid: "test_bug393285_12@tests.mozilla.org",
+ os: "Darwin",
+ xpcomabi: "ppc-gcc3,noarch-spidermonkey,x86-msvc",
+ versionRange: [],
+ },
+ {
+ guid: "test_bug393285_13@tests.mozilla.org",
+ os: "XPCShell",
+ xpcomabi: "ppc-gcc3,x86-msvc",
+ versionRange: [],
+ },
+ {
+ guid: "test_bug393285_14@tests.mozilla.org",
+ os: "XPCShell,WINNT",
+ xpcomabi: "ppc-gcc3,x86-msvc,noarch-spidermonkey",
+ versionRange: [],
+ },
+];
+
+add_task(async function setup() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
+ await promiseStartupManager();
+
+ for (let addon of ADDONS) {
+ await promiseInstallWebExtension({
+ manifest: {
+ name: addon.name,
+ version: addon.version,
+ browser_specific_settings: { gecko: { id: addon.id } },
+ },
+ });
+ }
+
+ let addons = await getAddons(ADDON_IDS);
+ for (let id of ADDON_IDS) {
+ equal(
+ addons.get(id).blocklistState,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+ `Add-on ${id} should not initially be blocked`
+ );
+ }
+});
+
+add_task(async function test_1() {
+ await AddonTestUtils.loadBlocklistRawData({ extensions: BLOCKLIST_DATA });
+
+ let addons = await getAddons(ADDON_IDS);
+ async function isBlocklisted(addon, appVer, toolkitVer) {
+ let state = await Blocklist.getAddonBlocklistState(
+ addon,
+ appVer,
+ toolkitVer
+ );
+ return state != Services.blocklist.STATE_NOT_BLOCKED;
+ }
+ for (let addon of ADDONS) {
+ let { id } = addon;
+ for (let blocklisted of addon.blocklisted || []) {
+ ok(
+ await isBlocklisted(addons.get(id), ...blocklisted),
+ `Add-on ${id} should be blocklisted in app/platform version ${blocklisted}`
+ );
+ }
+ for (let notBlocklisted of addon.notBlocklisted || []) {
+ ok(
+ !(await isBlocklisted(addons.get(id), ...notBlocklisted)),
+ `Add-on ${id} should not be blocklisted in app/platform version ${notBlocklisted}`
+ );
+ }
+ }
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_prefs.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_prefs.js
new file mode 100644
index 0000000000..42eb1305c4
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_prefs.js
@@ -0,0 +1,106 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests resetting of preferences in blocklist entry when an add-on is blocked.
+// See bug 802434.
+
+// useMLBF=true only supports blocking, not resetting prefs, since extensions
+// cannot set arbitrary prefs any more after the removal of legacy addons.
+enable_blocklist_v2_instead_of_useMLBF();
+
+const BLOCKLIST_DATA = [
+ {
+ guid: "block1@tests.mozilla.org",
+ versionRange: [
+ {
+ severity: "1",
+ targetApplication: [
+ {
+ guid: "xpcshell@tests.mozilla.org",
+ maxVersion: "2.*",
+ minVersion: "1",
+ },
+ ],
+ },
+ ],
+ prefs: ["test.blocklist.pref1", "test.blocklist.pref2"],
+ },
+ {
+ guid: "block2@tests.mozilla.org",
+ versionRange: [
+ {
+ severity: "3",
+ targetApplication: [
+ {
+ guid: "xpcshell@tests.mozilla.org",
+ maxVersion: "2.*",
+ minVersion: "1",
+ },
+ ],
+ },
+ ],
+ prefs: ["test.blocklist.pref3", "test.blocklist.pref4"],
+ },
+];
+
+add_task(async function setup() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+
+ await promiseStartupManager();
+
+ // Add 2 extensions
+ await promiseInstallWebExtension({
+ manifest: {
+ name: "Blocked add-on-1 with to-be-reset prefs",
+ version: "1.0",
+ browser_specific_settings: { gecko: { id: "block1@tests.mozilla.org" } },
+ },
+ });
+ await promiseInstallWebExtension({
+ manifest: {
+ name: "Blocked add-on-2 with to-be-reset prefs",
+ version: "1.0",
+ browser_specific_settings: { gecko: { id: "block2@tests.mozilla.org" } },
+ },
+ });
+
+ // Pre-set the preferences that we expect to get reset.
+ Services.prefs.setIntPref("test.blocklist.pref1", 15);
+ Services.prefs.setIntPref("test.blocklist.pref2", 15);
+ Services.prefs.setBoolPref("test.blocklist.pref3", true);
+ Services.prefs.setBoolPref("test.blocklist.pref4", true);
+
+ // Before blocklist is loaded.
+ let [a1, a2] = await AddonManager.getAddonsByIDs([
+ "block1@tests.mozilla.org",
+ "block2@tests.mozilla.org",
+ ]);
+ Assert.equal(a1.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ Assert.equal(a2.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+
+ Assert.equal(Services.prefs.getIntPref("test.blocklist.pref1"), 15);
+ Assert.equal(Services.prefs.getIntPref("test.blocklist.pref2"), 15);
+ Assert.equal(Services.prefs.getBoolPref("test.blocklist.pref3"), true);
+ Assert.equal(Services.prefs.getBoolPref("test.blocklist.pref4"), true);
+});
+
+add_task(async function test_blocks() {
+ await AddonTestUtils.loadBlocklistRawData({ extensions: BLOCKLIST_DATA });
+
+ // Blocklist changes should have applied and the prefs must be reset.
+ let [a1, a2] = await AddonManager.getAddonsByIDs([
+ "block1@tests.mozilla.org",
+ "block2@tests.mozilla.org",
+ ]);
+ Assert.notEqual(a1, null);
+ Assert.equal(a1.blocklistState, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ Assert.notEqual(a2, null);
+ Assert.equal(a2.blocklistState, Ci.nsIBlocklistService.STATE_BLOCKED);
+
+ // All these prefs must be reset to defaults.
+ Assert.equal(Services.prefs.prefHasUserValue("test.blocklist.pref1"), false);
+ Assert.equal(Services.prefs.prefHasUserValue("test.blocklist.pref2"), false);
+ Assert.equal(Services.prefs.prefHasUserValue("test.blocklist.pref3"), false);
+ Assert.equal(Services.prefs.prefHasUserValue("test.blocklist.pref4"), false);
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_regexp_split.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_regexp_split.js
new file mode 100644
index 0000000000..f48a6b9d8b
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_regexp_split.js
@@ -0,0 +1,225 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// useMLBF=true only supports blocking by version+ID, not by regexp.
+enable_blocklist_v2_instead_of_useMLBF();
+
+const BLOCKLIST_DATA = [
+ {
+ guid: "/^abcd.*/",
+ versionRange: [],
+ expectedType: "RegExp",
+ },
+ {
+ guid: "test@example.com",
+ versionRange: [],
+ expectedType: "string",
+ },
+ {
+ guid: "/^((a)|(b)|(c))$/",
+ versionRange: [],
+ expectedType: "Set",
+ },
+ {
+ guid: "/^((a@b)|(\\{6d9ddd6e-c6ee-46de-ab56-ce9080372b3\\})|(c@d.com))$/",
+ versionRange: [],
+ expectedType: "Set",
+ },
+ // The same as the above, but with escape sequences that disqualify it from
+ // being treated as a set (and a different guid)
+ {
+ guid: "/^((s@t)|(\\{6d9eee6e-c6ee-46de-ab56-ce9080372b3\\})|(c@d\\w.com))$/",
+ versionRange: [],
+ expectedType: "RegExp",
+ },
+ // Also the same, but with other magical regex characters.
+ // (and a different guid)
+ {
+ guid: "/^((u@v)|(\\{6d9fff6e*-c6ee-46de-ab56-ce9080372b3\\})|(c@dee?.com))$/",
+ versionRange: [],
+ expectedType: "RegExp",
+ },
+];
+
+/**
+ * Verify that both IDs being OR'd in a regex work,
+ * and that other regular expressions continue being
+ * used as regular expressions.
+ */
+add_task(async function test_check_matching_works() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
+ await promiseStartupManager();
+ await AddonTestUtils.loadBlocklistRawData({
+ extensions: BLOCKLIST_DATA,
+ });
+
+ const { BlocklistPrivate } = ChromeUtils.importESModule(
+ "resource://gre/modules/Blocklist.sys.mjs"
+ );
+ let parsedEntries = BlocklistPrivate.ExtensionBlocklistRS._entries;
+
+ // Unfortunately, the parsed entries aren't in the same order as the original.
+ function strForTypeOf(val) {
+ if (typeof val == "string") {
+ return "string";
+ }
+ if (val) {
+ return val.constructor.name;
+ }
+ return "other";
+ }
+ for (let type of ["Set", "RegExp", "string"]) {
+ let numberParsed = parsedEntries.filter(parsedEntry => {
+ return type == strForTypeOf(parsedEntry.matches.id);
+ }).length;
+ let expected = BLOCKLIST_DATA.filter(entry => {
+ return type == entry.expectedType;
+ }).length;
+ Assert.equal(
+ numberParsed,
+ expected,
+ type + " should have expected number of entries"
+ );
+ }
+ // Shouldn't block everything.
+ Assert.ok(
+ !(await Blocklist.getAddonBlocklistEntry({ id: "nonsense", version: "1" }))
+ );
+ // Should block IDs starting with abcd
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({ id: "abcde", version: "1" })
+ );
+ // Should block the literal string listed
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({
+ id: "test@example.com",
+ version: "1",
+ })
+ );
+ // Should block the IDs in (a)|(b)|(c)
+ Assert.ok(await Blocklist.getAddonBlocklistEntry({ id: "a", version: "1" }));
+ Assert.ok(await Blocklist.getAddonBlocklistEntry({ id: "b", version: "1" }));
+ Assert.ok(await Blocklist.getAddonBlocklistEntry({ id: "c", version: "1" }));
+ // Should block all the items processed to a set:
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({ id: "a@b", version: "1" })
+ );
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({
+ id: "{6d9ddd6e-c6ee-46de-ab56-ce9080372b3}",
+ version: "1",
+ })
+ );
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({ id: "c@d.com", version: "1" })
+ );
+ // Should block items that remained a regex:
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({ id: "s@t", version: "1" })
+ );
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({
+ id: "{6d9eee6e-c6ee-46de-ab56-ce9080372b3}",
+ version: "1",
+ })
+ );
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({ id: "c@dx.com", version: "1" })
+ );
+
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({ id: "u@v", version: "1" })
+ );
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({
+ id: "{6d9fff6eeeeeeee-c6ee-46de-ab56-ce9080372b3}",
+ version: "1",
+ })
+ );
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({ id: "c@dee.com", version: "1" })
+ );
+});
+
+// We should be checking all properties, not just the first one we come across.
+add_task(async function check_all_properties() {
+ await AddonTestUtils.loadBlocklistRawData({
+ extensions: [
+ {
+ guid: "literal@string.com",
+ creator: "Foo",
+ versionRange: [],
+ },
+ {
+ guid: "/regex.*@regex\\.com/",
+ creator: "Foo",
+ versionRange: [],
+ },
+ {
+ guid: "/^((set@set\\.com)|(anotherset@set\\.com)|(reallyenoughsetsalready@set\\.com))$/",
+ creator: "Foo",
+ versionRange: [],
+ },
+ ],
+ });
+
+ let { Blocklist } = ChromeUtils.importESModule(
+ "resource://gre/modules/Blocklist.sys.mjs"
+ );
+ // Check 'wrong' creator doesn't match.
+ Assert.ok(
+ !(await Blocklist.getAddonBlocklistEntry({
+ id: "literal@string.com",
+ version: "1",
+ creator: { name: "Bar" },
+ }))
+ );
+ Assert.ok(
+ !(await Blocklist.getAddonBlocklistEntry({
+ id: "regexaaaaa@regex.com",
+ version: "1",
+ creator: { name: "Bar" },
+ }))
+ );
+ Assert.ok(
+ !(await Blocklist.getAddonBlocklistEntry({
+ id: "set@set.com",
+ version: "1",
+ creator: { name: "Bar" },
+ }))
+ );
+
+ // Check 'wrong' ID doesn't match.
+ Assert.ok(
+ !(await Blocklist.getAddonBlocklistEntry({
+ id: "someotherid@foo.com",
+ version: "1",
+ creator: { name: "Foo" },
+ }))
+ );
+
+ // Check items matching all filters do match
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({
+ id: "literal@string.com",
+ version: "1",
+ creator: { name: "Foo" },
+ })
+ );
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({
+ id: "regexaaaaa@regex.com",
+ version: "1",
+ creator: { name: "Foo" },
+ })
+ );
+ Assert.ok(
+ await Blocklist.getAddonBlocklistEntry({
+ id: "set@set.com",
+ version: "1",
+ creator: { name: "Foo" },
+ })
+ );
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_severities.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_severities.js
new file mode 100644
index 0000000000..fffbb8a51e
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_severities.js
@@ -0,0 +1,504 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// useMLBF=true only supports one type of severity (hard block). The value of
+// appDisabled in the extension blocklist is checked in test_blocklist_mlbf.js.
+enable_blocklist_v2_instead_of_useMLBF();
+
+const URI_EXTENSION_BLOCKLIST_DIALOG =
+ "chrome://mozapps/content/extensions/blocklist.xhtml";
+
+// Workaround for Bug 658720 - URL formatter can leak during xpcshell tests
+const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL";
+Services.prefs.setCharPref(
+ PREF_BLOCKLIST_ITEM_URL,
+ "http://example.com/blocklist/%blockID%"
+);
+
+async function getAddonBlocklistURL(addon) {
+ let entry = await Blocklist.getAddonBlocklistEntry(addon);
+ return entry && entry.url;
+}
+
+var ADDONS = [
+ {
+ // Tests how the blocklist affects a disabled add-on
+ id: "test_bug455906_1@tests.mozilla.org",
+ name: "Bug 455906 Addon Test 1",
+ version: "5",
+ appVersion: "3",
+ },
+ {
+ // Tests how the blocklist affects an enabled add-on
+ id: "test_bug455906_2@tests.mozilla.org",
+ name: "Bug 455906 Addon Test 2",
+ version: "5",
+ appVersion: "3",
+ },
+ {
+ // Tests how the blocklist affects an enabled add-on, to be disabled by the notification
+ id: "test_bug455906_3@tests.mozilla.org",
+ name: "Bug 455906 Addon Test 3",
+ version: "5",
+ appVersion: "3",
+ },
+ {
+ // Tests how the blocklist affects a disabled add-on that was already warned about
+ id: "test_bug455906_4@tests.mozilla.org",
+ name: "Bug 455906 Addon Test 4",
+ version: "5",
+ appVersion: "3",
+ },
+ {
+ // Tests how the blocklist affects an enabled add-on that was already warned about
+ id: "test_bug455906_5@tests.mozilla.org",
+ name: "Bug 455906 Addon Test 5",
+ version: "5",
+ appVersion: "3",
+ },
+ {
+ // Tests how the blocklist affects an already blocked add-on
+ id: "test_bug455906_6@tests.mozilla.org",
+ name: "Bug 455906 Addon Test 6",
+ version: "5",
+ appVersion: "3",
+ },
+ {
+ // Tests how the blocklist affects an incompatible add-on
+ id: "test_bug455906_7@tests.mozilla.org",
+ name: "Bug 455906 Addon Test 7",
+ version: "5",
+ appVersion: "2",
+ },
+ {
+ // Spare add-on used to ensure we get a notification when switching lists
+ id: "dummy_bug455906_1@tests.mozilla.org",
+ name: "Dummy Addon 1",
+ version: "5",
+ appVersion: "3",
+ },
+ {
+ // Spare add-on used to ensure we get a notification when switching lists
+ id: "dummy_bug455906_2@tests.mozilla.org",
+ name: "Dummy Addon 2",
+ version: "5",
+ appVersion: "3",
+ },
+];
+
+var gNotificationCheck = null;
+
+// Don't need the full interface, attempts to call other methods will just
+// throw which is just fine
+var WindowWatcher = {
+ openWindow(parent, url, name, features, windowArguments) {
+ // Should be called to list the newly blocklisted items
+ equal(url, URI_EXTENSION_BLOCKLIST_DIALOG);
+
+ if (gNotificationCheck) {
+ gNotificationCheck(windowArguments.wrappedJSObject);
+ }
+
+ // run the code after the blocklist is closed
+ Services.obs.notifyObservers(null, "addon-blocklist-closed");
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIWindowWatcher"]),
+};
+
+MockRegistrar.register(
+ "@mozilla.org/embedcomp/window-watcher;1",
+ WindowWatcher
+);
+
+function createAddon(addon) {
+ return promiseInstallWebExtension({
+ manifest: {
+ name: addon.name,
+ version: addon.version,
+ browser_specific_settings: {
+ gecko: {
+ id: addon.id,
+ strict_min_version: addon.appVersion,
+ strict_max_version: addon.appVersion,
+ },
+ },
+ },
+ });
+}
+
+const BLOCKLIST_DATA = {
+ start: {
+ // Block 4-6 and a dummy:
+ extensions: [
+ {
+ guid: "test_bug455906_4@tests.mozilla.org",
+ versionRange: [{ severity: "-1" }],
+ },
+ {
+ guid: "test_bug455906_5@tests.mozilla.org",
+ versionRange: [{ severity: "1" }],
+ },
+ {
+ guid: "test_bug455906_6@tests.mozilla.org",
+ versionRange: [{ severity: "2" }],
+ },
+ {
+ guid: "dummy_bug455906_1@tests.mozilla.org",
+ versionRange: [],
+ },
+ ],
+ },
+ warn: {
+ // warn for all test add-ons:
+ extensions: ADDONS.filter(a => a.id.startsWith("test_")).map(a => ({
+ guid: a.id,
+ versionRange: [{ severity: "-1" }],
+ })),
+ },
+ block: {
+ // block all test add-ons:
+ extensions: ADDONS.filter(a => a.id.startsWith("test_")).map(a => ({
+ guid: a.id,
+ blockID: a.id,
+ versionRange: [],
+ })),
+ },
+ empty: {
+ // Block a dummy so there's a change:
+ extensions: [
+ {
+ guid: "dummy_bug455906_2@tests.mozilla.org",
+ versionRange: [],
+ },
+ ],
+ },
+};
+
+async function loadBlocklist(id, callback) {
+ gNotificationCheck = callback;
+
+ await AddonTestUtils.loadBlocklistRawData(BLOCKLIST_DATA[id]);
+}
+
+function create_blocklistURL(blockID) {
+ let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
+ url = url.replace(/%blockID%/g, blockID);
+ return url;
+}
+
+// Before every main test this is the state the add-ons are meant to be in
+async function checkInitialState() {
+ let addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id));
+
+ checkAddonState(addons[0], {
+ userDisabled: true,
+ softDisabled: false,
+ appDisabled: false,
+ });
+ checkAddonState(addons[1], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: false,
+ });
+ checkAddonState(addons[2], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: false,
+ });
+ checkAddonState(addons[3], {
+ userDisabled: true,
+ softDisabled: true,
+ appDisabled: false,
+ });
+ checkAddonState(addons[4], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: false,
+ });
+ checkAddonState(addons[5], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: true,
+ });
+ checkAddonState(addons[6], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: true,
+ });
+}
+
+function checkAddonState(addon, state) {
+ return checkAddon(addon.id, addon, state);
+}
+
+add_task(async function setup() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "3");
+
+ await promiseStartupManager();
+
+ // Load the initial blocklist into the profile to check add-ons start in the
+ // right state.
+ await AddonTestUtils.loadBlocklistRawData(BLOCKLIST_DATA.start);
+
+ for (let addon of ADDONS) {
+ await createAddon(addon);
+ }
+});
+
+add_task(async function test_1() {
+ // Tests the add-ons were installed and the initial blocklist applied as expected
+
+ let addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id));
+ for (var i = 0; i < ADDONS.length; i++) {
+ ok(addons[i], `Addon ${i + 1} should be installed correctly`);
+ }
+
+ checkAddonState(addons[0], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: false,
+ });
+ checkAddonState(addons[1], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: false,
+ });
+ checkAddonState(addons[2], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: false,
+ });
+
+ // Warn add-ons should be soft disabled automatically
+ checkAddonState(addons[3], {
+ userDisabled: true,
+ softDisabled: true,
+ appDisabled: false,
+ });
+ checkAddonState(addons[4], {
+ userDisabled: true,
+ softDisabled: true,
+ appDisabled: false,
+ });
+
+ // Blocked and incompatible should be app disabled only
+ checkAddonState(addons[5], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: true,
+ });
+ checkAddonState(addons[6], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: true,
+ });
+
+ // Put the add-ons into the base state
+ await addons[0].disable();
+ await addons[4].enable();
+
+ await promiseRestartManager();
+ await checkInitialState();
+
+ await loadBlocklist("warn", args => {
+ dump("Checking notification pt 2\n");
+ // This test is artificial, we don't notify for add-ons anymore, see
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1257565#c111 . Cleaning this up
+ // should happen but this patchset is too huge as it is so I'm deferring it.
+ equal(args.list.length, 2);
+ });
+
+ await promiseRestartManager();
+ dump("Checking results pt 2\n");
+
+ addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id));
+
+ info("Should have disabled this add-on as requested");
+ checkAddonState(addons[2], {
+ userDisabled: true,
+ softDisabled: true,
+ appDisabled: false,
+ });
+
+ info("The blocked add-on should have changed to soft disabled");
+ checkAddonState(addons[5], {
+ userDisabled: true,
+ softDisabled: true,
+ appDisabled: false,
+ });
+ checkAddonState(addons[6], {
+ userDisabled: true,
+ softDisabled: true,
+ appDisabled: true,
+ });
+
+ info("These should have been unchanged");
+ checkAddonState(addons[0], {
+ userDisabled: true,
+ softDisabled: false,
+ appDisabled: false,
+ });
+ // XXXgijs this is supposed to be not user disabled or soft disabled, but because we don't show
+ // the dialog, it's disabled anyway. Comment out this assertion for now...
+ // checkAddonState(addons[1], {userDisabled: false, softDisabled: false, appDisabled: false});
+ checkAddonState(addons[3], {
+ userDisabled: true,
+ softDisabled: true,
+ appDisabled: false,
+ });
+ checkAddonState(addons[4], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: false,
+ });
+
+ // Back to starting state
+ await addons[2].enable();
+ await addons[5].enable();
+
+ await promiseRestartManager();
+ await loadBlocklist("start");
+});
+
+add_task(async function test_pt3() {
+ await promiseRestartManager();
+ await checkInitialState();
+
+ await loadBlocklist("block", args => {
+ dump("Checking notification pt 3\n");
+ equal(args.list.length, 3);
+ });
+
+ await promiseRestartManager();
+ dump("Checking results pt 3\n");
+
+ let addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id));
+
+ // All should have gained the blocklist state, user disabled as previously
+ checkAddonState(addons[0], {
+ userDisabled: true,
+ softDisabled: false,
+ appDisabled: true,
+ });
+ checkAddonState(addons[1], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: true,
+ });
+ checkAddonState(addons[2], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: true,
+ });
+ checkAddonState(addons[4], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: true,
+ });
+
+ // Should have gained the blocklist state but no longer be soft disabled
+ checkAddonState(addons[3], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: true,
+ });
+
+ // Check blockIDs are correct
+ equal(
+ await getAddonBlocklistURL(addons[0]),
+ create_blocklistURL(addons[0].id)
+ );
+ equal(
+ await getAddonBlocklistURL(addons[1]),
+ create_blocklistURL(addons[1].id)
+ );
+ equal(
+ await getAddonBlocklistURL(addons[2]),
+ create_blocklistURL(addons[2].id)
+ );
+ equal(
+ await getAddonBlocklistURL(addons[3]),
+ create_blocklistURL(addons[3].id)
+ );
+ equal(
+ await getAddonBlocklistURL(addons[4]),
+ create_blocklistURL(addons[4].id)
+ );
+
+ // Shouldn't be changed
+ checkAddonState(addons[5], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: true,
+ });
+ checkAddonState(addons[6], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: true,
+ });
+
+ // Back to starting state
+ await loadBlocklist("start");
+});
+
+add_task(async function test_pt4() {
+ let addon = await AddonManager.getAddonByID(ADDONS[4].id);
+ await addon.enable();
+
+ await promiseRestartManager();
+ await checkInitialState();
+
+ await loadBlocklist("empty", args => {
+ dump("Checking notification pt 4\n");
+ // See note in other callback - we no longer notify for non-blocked add-ons.
+ ok(false, "Should not get a notification as there are no blocked addons.");
+ });
+
+ await promiseRestartManager();
+ dump("Checking results pt 4\n");
+
+ let addons = await AddonManager.getAddonsByIDs(ADDONS.map(a => a.id));
+ // This should have become unblocked
+ checkAddonState(addons[5], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: false,
+ });
+
+ // Should get re-enabled
+ checkAddonState(addons[3], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: false,
+ });
+
+ // No change for anything else
+ checkAddonState(addons[0], {
+ userDisabled: true,
+ softDisabled: false,
+ appDisabled: false,
+ });
+ checkAddonState(addons[1], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: false,
+ });
+ checkAddonState(addons[2], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: false,
+ });
+ checkAddonState(addons[4], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: false,
+ });
+ checkAddonState(addons[6], {
+ userDisabled: false,
+ softDisabled: false,
+ appDisabled: true,
+ });
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js
new file mode 100644
index 0000000000..ccaa1868fe
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_statechange_telemetry.js
@@ -0,0 +1,411 @@
+// Verifies that changes to blocklistState are correctly reported to telemetry.
+
+"use strict";
+
+Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true);
+
+// Set min version to 42 because the updater defaults to min version 42.
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "42.0", "42.0");
+
+// Use unprivileged signatures because the MLBF-based blocklist does not
+// apply to add-ons with a privileged signature.
+AddonTestUtils.usePrivilegedSignatures = false;
+
+const { Downloader } = ChromeUtils.importESModule(
+ "resource://services-settings/Attachments.sys.mjs"
+);
+
+const { TelemetryController } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryController.sys.mjs"
+);
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+const ExtensionBlocklistMLBF = getExtensionBlocklistMLBF();
+
+const EXT_ID = "maybeblockme@tests.mozilla.org";
+
+// The addon blocked by the bloom filter (referenced by MLBF_RECORD).
+const EXT_BLOCKED_ID = "@blocked";
+const EXT_BLOCKED_VERSION = "1";
+const EXT_BLOCKED_SIGN_TIME = 12345; // Before MLBF_RECORD.generation_time.
+
+// To serve updates.
+const server = AddonTestUtils.createHttpServer();
+const SERVER_BASE_URL = `http://127.0.0.1:${server.identity.primaryPort}`;
+const SERVER_UPDATE_PATH = "/update.json";
+const SERVER_UPDATE_URL = `${SERVER_BASE_URL}${SERVER_UPDATE_PATH}`;
+// update is served via `server` over insecure http.
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+
+async function assertEventDetails(expectedExtras) {
+ if (!IS_ANDROID_BUILD) {
+ const expectedEvents = expectedExtras.map(expectedExtra => {
+ let { object, value, ...extra } = expectedExtra;
+ return ["blocklist", "addonBlockChange", object, value, extra];
+ });
+ await TelemetryTestUtils.assertEvents(expectedEvents, {
+ category: "blocklist",
+ method: "addonBlockChange",
+ });
+ } else {
+ info(
+ `Skip assertions on collected samples for addonBlockChange on android builds`
+ );
+ }
+ assertGleanEventDetails(expectedExtras);
+}
+async function assertGleanEventDetails(expectedExtras) {
+ const snapshot = testGetValue(Glean.blocklist.addonBlockChange);
+ if (expectedExtras.length === 0) {
+ Assert.deepEqual(undefined, snapshot, "Expected zero addonBlockChange");
+ return;
+ }
+ Assert.equal(
+ expectedExtras.length,
+ snapshot?.length,
+ "Number of addonBlockChange records"
+ );
+ for (let i of expectedExtras.keys()) {
+ let actual = snapshot[i].extra;
+ // Glean uses snake_case instead of camelCase.
+ let { blocklistState, ...expected } = expectedExtras[i];
+ expected.blocklist_state = blocklistState;
+ Assert.deepEqual(expected, actual, `Expected addonBlockChange (${i})`);
+ }
+}
+
+// Stage an update on the update server. The add-on must have been created
+// with update_url set to SERVER_UPDATE_URL.
+function setupAddonUpdate(addonId, addonVersion) {
+ let updateXpi = AddonTestUtils.createTempWebExtensionFile({
+ manifest: {
+ version: addonVersion,
+ browser_specific_settings: {
+ gecko: { id: addonId, update_url: SERVER_UPDATE_URL },
+ },
+ },
+ });
+ let updateXpiPath = `/update-${addonId}-${addonVersion}.xpi`;
+ server.registerFile(updateXpiPath, updateXpi);
+ AddonTestUtils.registerJSON(server, SERVER_UPDATE_PATH, {
+ addons: {
+ [addonId]: {
+ updates: [
+ {
+ version: addonVersion,
+ update_link: `${SERVER_BASE_URL}${updateXpiPath}`,
+ },
+ ],
+ },
+ },
+ });
+}
+
+async function tryAddonInstall(addonId, addonVersion) {
+ let xpiFile = AddonTestUtils.createTempWebExtensionFile({
+ manifest: {
+ version: addonVersion,
+ browser_specific_settings: {
+ gecko: { id: addonId, update_url: SERVER_UPDATE_URL },
+ },
+ },
+ });
+ const install = await promiseInstallFile(xpiFile, true);
+ // Passing true to promiseInstallFile means that the xpi may not be installed
+ // if blocked by the blocklist. In that case, |install| may be null.
+ return install?.addon;
+}
+
+add_task(async function setup() {
+ if (!IS_ANDROID_BUILD) {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+ // FOG needs to be initialized in order for data to flow.
+ Services.fog.initializeFOG();
+ }
+ await TelemetryController.testSetup();
+
+ // Disable the packaged record and attachment to make sure that the test
+ // will not fall back to the packaged attachments.
+ Downloader._RESOURCE_BASE_URL = "invalid://bogus";
+
+ await promiseStartupManager();
+});
+
+add_task(async function install_update_not_blocked_is_no_events() {
+ resetBlocklistTelemetry();
+ // Install an add-on that is not blocked. Then update to the next version.
+ let addon = await tryAddonInstall(EXT_ID, "0.1");
+
+ // Version "1" not blocked yet, but will be in the next test task.
+ setupAddonUpdate(EXT_ID, "1");
+ let update = await AddonTestUtils.promiseFindAddonUpdates(addon);
+ await promiseCompleteInstall(update.updateAvailable);
+ addon = await AddonManager.getAddonByID(EXT_ID);
+ equal(addon.version, "1", "Add-on was updated");
+
+ await assertEventDetails([]);
+});
+
+add_task(async function blocklist_update_events() {
+ resetBlocklistTelemetry();
+ const EXT_HOURS_SINCE_INSTALL = 4321;
+ const addon = await AddonManager.getAddonByID(EXT_ID);
+ addon.__AddonInternal__.installDate =
+ addon.installDate.getTime() - 3600000 * EXT_HOURS_SINCE_INSTALL;
+
+ await AddonTestUtils.loadBlocklistRawData({
+ extensionsMLBF: [
+ { stash: { blocked: [`${EXT_ID}:1`], unblocked: [] }, stash_time: 123 },
+ { stash: { blocked: [`${EXT_ID}:2`], unblocked: [] }, stash_time: 456 },
+ ],
+ });
+
+ await assertEventDetails([
+ {
+ object: "blocklist_update",
+ value: EXT_ID,
+ blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED
+ addon_version: "1",
+ signed_date: "0",
+ hours_since: `${EXT_HOURS_SINCE_INSTALL}`,
+ mlbf_last_time: "456",
+ mlbf_generation: "0",
+ mlbf_source: "unknown",
+ },
+ ]);
+});
+
+add_task(async function update_check_blocked_by_stash() {
+ resetBlocklistTelemetry();
+ setupAddonUpdate(EXT_ID, "2");
+ let addon = await AddonManager.getAddonByID(EXT_ID);
+ let update = await AddonTestUtils.promiseFindAddonUpdates(addon);
+ // Blocks in stashes are immediately enforced by update checks.
+ // Blocks stored in MLBFs are only enforced after the package is downloaded,
+ // and that scenario is covered by update_check_blocked_by_stash elsewhere.
+ equal(update.updateAvailable, false, "Update was blocked by stash");
+
+ await assertEventDetails([
+ {
+ object: "addon_update_check",
+ value: EXT_ID,
+ blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED
+ addon_version: "2",
+ signed_date: "0",
+ hours_since: "-1",
+ mlbf_last_time: "456",
+ mlbf_generation: "0",
+ mlbf_source: "unknown",
+ },
+ ]);
+});
+
+// Any attempt to re-install a blocked add-on should trigger a telemetry
+// event, even though the blocklistState did not change.
+add_task(async function reinstall_blocked_addon() {
+ resetBlocklistTelemetry();
+ let blockedAddon = await AddonManager.getAddonByID(EXT_ID);
+ equal(
+ blockedAddon.blocklistState,
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "Addon was initially blocked"
+ );
+
+ let addon = await tryAddonInstall(EXT_ID, "2");
+ ok(!addon, "Add-on install should be blocked by a stash");
+
+ await assertEventDetails([
+ {
+ // Note: installs of existing versions are observed as "addon_install".
+ // Only updates after update checks are tagged as "addon_update".
+ object: "addon_install",
+ value: EXT_ID,
+ blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED
+ addon_version: "2",
+ signed_date: "0",
+ hours_since: "-1",
+ mlbf_last_time: "456",
+ mlbf_generation: "0",
+ mlbf_source: "unknown",
+ },
+ ]);
+});
+
+// For comparison with the next test task (database_modified), verify that a
+// regular restart without database modifications does not trigger events.
+add_task(async function regular_restart_no_event() {
+ resetBlocklistTelemetry();
+ // Version different/higher than the 42.0 that was passed to createAppInfo at
+ // the start of this test file to force a database rebuild.
+ await promiseRestartManager("90.0");
+ await assertEventDetails([]);
+
+ await promiseRestartManager();
+ await assertEventDetails([]);
+});
+
+add_task(async function database_modified() {
+ resetBlocklistTelemetry();
+ const EXT_HOURS_SINCE_INSTALL = 3;
+ await promiseShutdownManager();
+
+ // Modify the addon database: blocked->not blocked + decrease installDate.
+ let addonDB = await IOUtils.readJSON(gExtensionsJSON.path);
+ let rawAddon = addonDB.addons[0];
+ equal(rawAddon.id, EXT_ID, "Expected entry in addonDB");
+ equal(rawAddon.blocklistState, 2, "Expected STATE_BLOCKED");
+ rawAddon.blocklistState = 0; // STATE_NOT_BLOCKED
+ rawAddon.installDate = Date.now() - 3600000 * EXT_HOURS_SINCE_INSTALL;
+ await IOUtils.writeJSON(gExtensionsJSON.path, addonDB);
+
+ // Bump version to force database rebuild.
+ await promiseStartupManager("91.0");
+ // Shut down because the database reconcilation blocks shutdown, and we want
+ // to be certain that the process has finished before checking the events.
+ await promiseShutdownManager();
+ await assertEventDetails([
+ {
+ object: "addon_db_modified",
+ value: EXT_ID,
+ blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED
+ addon_version: "1",
+ signed_date: "0",
+ hours_since: `${EXT_HOURS_SINCE_INSTALL}`,
+ mlbf_last_time: "456",
+ mlbf_generation: "0",
+ mlbf_source: "unknown",
+ },
+ ]);
+
+ resetBlocklistTelemetry();
+ await promiseStartupManager();
+ await assertEventDetails([]);
+});
+
+add_task(async function install_replaces_blocked_addon() {
+ resetBlocklistTelemetry();
+ let addon = await tryAddonInstall(EXT_ID, "3");
+ ok(addon, "Update supersedes blocked add-on");
+
+ await assertEventDetails([
+ {
+ object: "addon_install",
+ value: EXT_ID,
+ blocklistState: "0", // Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ addon_version: "3",
+ signed_date: "0",
+ hours_since: "-1",
+ mlbf_last_time: "456",
+ mlbf_generation: "0",
+ mlbf_source: "unknown",
+ },
+ ]);
+});
+
+add_task(async function install_blocked_by_mlbf() {
+ resetBlocklistTelemetry();
+ await ExtensionBlocklistMLBF._client.db.saveAttachment(
+ ExtensionBlocklistMLBF.RS_ATTACHMENT_ID,
+ { record: MLBF_RECORD, blob: await load_mlbf_record_as_blob() }
+ );
+ await AddonTestUtils.loadBlocklistRawData({
+ extensionsMLBF: [MLBF_RECORD],
+ });
+
+ AddonTestUtils.certSignatureDate = EXT_BLOCKED_SIGN_TIME;
+ let addon = await tryAddonInstall(EXT_BLOCKED_ID, EXT_BLOCKED_VERSION);
+ AddonTestUtils.certSignatureDate = null;
+
+ ok(!addon, "Add-on install should be blocked by the MLBF");
+
+ await assertEventDetails([
+ {
+ object: "addon_install",
+ value: EXT_BLOCKED_ID,
+ blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED
+ addon_version: EXT_BLOCKED_VERSION,
+ signed_date: `${EXT_BLOCKED_SIGN_TIME}`,
+ hours_since: "-1",
+ // When there is no stash at all, the MLBF's generation_time is used.
+ mlbf_last_time: `${MLBF_RECORD.generation_time}`,
+ mlbf_generation: `${MLBF_RECORD.generation_time}`,
+ mlbf_source: "cache_match",
+ },
+ ]);
+});
+
+// A limitation of the MLBF-based blocklist is that it needs the add-on package
+// in order to check its signature date.
+// This part of the test verifies that installation of the add-on is blocked,
+// despite the update check tentatively accepting the package.
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=1649896 for rationale.
+add_task(async function update_check_blocked_by_mlbf() {
+ resetBlocklistTelemetry();
+ // Install a version that we can update, lower than EXT_BLOCKED_VERSION.
+ let addon = await tryAddonInstall(EXT_BLOCKED_ID, "0.1");
+
+ setupAddonUpdate(EXT_BLOCKED_ID, EXT_BLOCKED_VERSION);
+ AddonTestUtils.certSignatureDate = EXT_BLOCKED_SIGN_TIME;
+ let update = await AddonTestUtils.promiseFindAddonUpdates(addon);
+ ok(update.updateAvailable, "Update was not blocked by stash");
+
+ await promiseCompleteInstall(update.updateAvailable);
+ AddonTestUtils.certSignatureDate = null;
+
+ addon = await AddonManager.getAddonByID(EXT_BLOCKED_ID);
+ equal(addon.version, EXT_BLOCKED_VERSION, "Add-on was updated");
+ equal(
+ addon.blocklistState,
+ Ci.nsIBlocklistService.STATE_BLOCKED,
+ "Add-on is blocked"
+ );
+ equal(addon.appDisabled, true, "Add-on was disabled because of the block");
+
+ await assertEventDetails([
+ {
+ object: "addon_update",
+ value: EXT_BLOCKED_ID,
+ blocklistState: "2", // Ci.nsIBlocklistService.STATE_BLOCKED
+ addon_version: EXT_BLOCKED_VERSION,
+ signed_date: `${EXT_BLOCKED_SIGN_TIME}`,
+ hours_since: "-1",
+ mlbf_last_time: `${MLBF_RECORD.generation_time}`,
+ mlbf_generation: `${MLBF_RECORD.generation_time}`,
+ mlbf_source: "cache_match",
+ },
+ ]);
+});
+
+add_task(async function update_blocked_to_unblocked() {
+ resetBlocklistTelemetry();
+ // was blocked in update_check_blocked_by_mlbf.
+ let blockedAddon = await AddonManager.getAddonByID(EXT_BLOCKED_ID);
+
+ // 3 is higher than EXT_BLOCKED_VERSION.
+ setupAddonUpdate(EXT_BLOCKED_ID, "3");
+ AddonTestUtils.certSignatureDate = EXT_BLOCKED_SIGN_TIME;
+ let update = await AddonTestUtils.promiseFindAddonUpdates(blockedAddon);
+ ok(update.updateAvailable, "Found an update");
+
+ await promiseCompleteInstall(update.updateAvailable);
+ AddonTestUtils.certSignatureDate = null;
+
+ let addon = await AddonManager.getAddonByID(EXT_BLOCKED_ID);
+ equal(addon.appDisabled, false, "Add-on was re-enabled after unblock");
+ await assertEventDetails([
+ {
+ object: "addon_update",
+ value: EXT_BLOCKED_ID,
+ blocklistState: "0", // Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ addon_version: "3",
+ signed_date: `${EXT_BLOCKED_SIGN_TIME}`,
+ hours_since: "-1",
+ mlbf_last_time: `${MLBF_RECORD.generation_time}`,
+ mlbf_generation: `${MLBF_RECORD.generation_time}`,
+ mlbf_source: "cache_match",
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_targetapp_filter.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_targetapp_filter.js
new file mode 100644
index 0000000000..b48700570e
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_targetapp_filter.js
@@ -0,0 +1,392 @@
+const { BlocklistPrivate } = ChromeUtils.importESModule(
+ "resource://gre/modules/Blocklist.sys.mjs"
+);
+const { RemoteSettings } = ChromeUtils.importESModule(
+ "resource://services-settings/remote-settings.sys.mjs"
+);
+
+const APP_ID = "xpcshell@tests.mozilla.org";
+const TOOLKIT_ID = "toolkit@mozilla.org";
+
+let client;
+
+async function clear_state() {
+ // Clear local DB.
+ await client.db.clear();
+}
+
+async function createRecords(records) {
+ const withId = records.map((record, i) => ({
+ id: `record-${i}`,
+ ...record,
+ }));
+ // Prevent packaged dump to be loaded with high collection timestamp
+ return client.db.importChanges({}, Date.now(), withId);
+}
+
+function run_test() {
+ AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ "58",
+ ""
+ );
+ // This will initialize the remote settings clients for blocklists,
+ // with their specific options etc.
+ BlocklistPrivate.ExtensionBlocklistRS.ensureInitialized();
+ // Obtain one of the instantiated client for our tests.
+ client = RemoteSettings("addons", { bucketName: "blocklists" });
+
+ run_next_test();
+}
+
+add_task(async function test_supports_filter_expressions() {
+ await createRecords([
+ {
+ name: "My Extension",
+ filter_expression: 'env.appinfo.ID == "xpcshell@tests.mozilla.org"',
+ },
+ {
+ name: "My Extension",
+ filter_expression: "1 == 2",
+ },
+ ]);
+
+ const list = await client.get();
+ equal(list.length, 1);
+});
+add_task(clear_state);
+
+add_task(async function test_returns_all_without_target() {
+ await createRecords([
+ {
+ name: "My Extension",
+ },
+ {
+ name: "foopydoo",
+ versionRange: [],
+ },
+ {
+ name: "My Other Extension",
+ versionRange: [
+ {
+ severity: 0,
+ targetApplication: [],
+ },
+ ],
+ },
+ {
+ name: "Java(\\(TM\\))? Plug-in 11\\.(7[6-9]|[8-9]\\d|1([0-6]\\d|70))(\\.\\d+)?([^\\d\\._]|$)",
+ versionRange: [
+ {
+ severity: 0,
+ },
+ ],
+ matchFilename: "libnpjp2\\.so",
+ },
+ {
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [],
+ maxVersion: "1",
+ minVersion: "0",
+ severity: "1",
+ },
+ ],
+ },
+ ]);
+
+ const list = await client.get();
+ equal(list.length, 5);
+});
+add_task(clear_state);
+
+add_task(async function test_returns_without_guid_or_with_matching_guid() {
+ await createRecords([
+ {
+ willMatch: true,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [{}],
+ },
+ ],
+ },
+ {
+ willMatch: false,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: "some-guid",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ willMatch: true,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: APP_ID,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ willMatch: true,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: TOOLKIT_ID,
+ },
+ ],
+ },
+ ],
+ },
+ ]);
+
+ const list = await client.get();
+ info(JSON.stringify(list, null, 2));
+ equal(list.length, 3);
+ ok(list.every(e => e.willMatch));
+});
+add_task(clear_state);
+
+add_task(
+ async function test_returns_without_app_version_or_with_matching_version() {
+ await createRecords([
+ {
+ willMatch: true,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: APP_ID,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ willMatch: true,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: APP_ID,
+ minVersion: "0",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ willMatch: true,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: APP_ID,
+ minVersion: "0",
+ maxVersion: "9999",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ willMatch: false,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: APP_ID,
+ minVersion: "0",
+ maxVersion: "1",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ willMatch: true,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: TOOLKIT_ID,
+ minVersion: "0",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ willMatch: true,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: TOOLKIT_ID,
+ minVersion: "0",
+ maxVersion: "9999",
+ },
+ ],
+ },
+ ],
+ // We can't test the false case with maxVersion for toolkit, because the toolkit version
+ // is 0 in xpcshell.
+ },
+ ]);
+
+ const list = await client.get();
+ equal(list.length, 5);
+ ok(list.every(e => e.willMatch));
+ }
+);
+add_task(clear_state);
+
+add_task(async function test_multiple_version_and_target_applications() {
+ await createRecords([
+ {
+ willMatch: true,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: "other-guid",
+ },
+ ],
+ },
+ {
+ targetApplication: [
+ {
+ guid: APP_ID,
+ minVersion: "0",
+ maxVersion: "*",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ willMatch: true,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: "other-guid",
+ },
+ ],
+ },
+ {
+ targetApplication: [
+ {
+ guid: APP_ID,
+ minVersion: "0",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ willMatch: false,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: APP_ID,
+ maxVersion: "57.*",
+ },
+ ],
+ },
+ {
+ targetApplication: [
+ {
+ guid: APP_ID,
+ maxVersion: "56.*",
+ },
+ {
+ guid: APP_ID,
+ maxVersion: "57.*",
+ },
+ ],
+ },
+ ],
+ },
+ ]);
+
+ const list = await client.get();
+ equal(list.length, 2);
+ ok(list.every(e => e.willMatch));
+});
+add_task(clear_state);
+
+add_task(async function test_complex_version() {
+ await createRecords([
+ {
+ willMatch: false,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: APP_ID,
+ maxVersion: "57.*",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ willMatch: true,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: APP_ID,
+ maxVersion: "9999.*",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ willMatch: true,
+ name: "foopydoo",
+ versionRange: [
+ {
+ targetApplication: [
+ {
+ guid: APP_ID,
+ minVersion: "19.0a1",
+ },
+ ],
+ },
+ ],
+ },
+ ]);
+
+ const list = await client.get();
+ equal(list.length, 2);
+});
+add_task(clear_state);
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_telemetry.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_telemetry.js
new file mode 100644
index 0000000000..cf1992b121
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklist_telemetry.js
@@ -0,0 +1,138 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+AddonTestUtils.init(this);
+AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ "1",
+ "49"
+);
+
+const { TelemetryController } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryController.sys.mjs"
+);
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+add_setup({ skip_if: () => IS_ANDROID_BUILD }, function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // FOG needs to be initialized in order for data to flow.
+ Services.fog.initializeFOG();
+});
+
+function assertTelemetryScalars(expectedScalars) {
+ if (!IS_ANDROID_BUILD) {
+ let scalars = TelemetryTestUtils.getProcessScalars("parent");
+
+ for (const scalarName of Object.keys(expectedScalars || {})) {
+ equal(
+ scalars[scalarName],
+ expectedScalars[scalarName],
+ `Got the expected value for ${scalarName} scalar`
+ );
+ }
+ } else {
+ info(
+ `Skip assertions on collected samples for ${expectedScalars} on android builds`
+ );
+ }
+}
+
+add_task(async function test_setup() {
+ // Ensure that the telemetry scalar definitions are loaded and the
+ // AddonManager initialized.
+ await TelemetryController.testSetup();
+ await AddonTestUtils.promiseStartupManager();
+});
+
+add_task(async function test_blocklist_lastModified_rs_scalars() {
+ resetBlocklistTelemetry();
+ const now = Date.now();
+
+ const lastEntryTimes = {
+ addons: now - 5000,
+ addons_mlbf: now - 4000,
+ };
+
+ const lastEntryTimesUTC = {};
+ const toUTC = t => new Date(t).toUTCString();
+ for (const key of Object.keys(lastEntryTimes)) {
+ lastEntryTimesUTC[key] = toUTC(lastEntryTimes[key]);
+ }
+
+ const {
+ BlocklistPrivate: {
+ BlocklistTelemetry,
+ ExtensionBlocklistMLBF,
+ ExtensionBlocklistRS,
+ },
+ } = ChromeUtils.importESModule("resource://gre/modules/Blocklist.sys.mjs");
+
+ // Return a promise resolved when the recordRSBlocklistLastModified method
+ // has been called (by temporarily replacing the method with a function that
+ // calls the real method and then resolve the promise).
+ function promiseScalarRecorded() {
+ return new Promise(resolve => {
+ let origFn = BlocklistTelemetry.recordRSBlocklistLastModified;
+ BlocklistTelemetry.recordRSBlocklistLastModified = async (...args) => {
+ BlocklistTelemetry.recordRSBlocklistLastModified = origFn;
+ let res = await origFn.apply(BlocklistTelemetry, args);
+ resolve();
+ return res;
+ };
+ });
+ }
+
+ async function fakeRemoteSettingsSync(rsClient, lastModified) {
+ await rsClient.db.importChanges({}, lastModified);
+ await rsClient.emit("sync");
+ }
+
+ assertTelemetryScalars({
+ "blocklist.lastModified_rs_addons_mlbf": undefined,
+ });
+ Assert.equal(
+ undefined,
+ testGetValue(Glean.blocklist.lastModifiedRsAddonsMblf)
+ );
+
+ info("Test RS addon blocklist lastModified scalar");
+
+ await ExtensionBlocklistRS.ensureInitialized();
+ await Promise.all([
+ promiseScalarRecorded(),
+ fakeRemoteSettingsSync(ExtensionBlocklistRS._client, lastEntryTimes.addons),
+ ]);
+
+ assertTelemetryScalars({
+ "blocklist.lastModified_rs_addons_mlbf": undefined,
+ });
+
+ Assert.equal(
+ undefined,
+ testGetValue(Glean.blocklist.lastModifiedRsAddonsMblf)
+ );
+
+ await ExtensionBlocklistMLBF.ensureInitialized();
+ await Promise.all([
+ promiseScalarRecorded(),
+ fakeRemoteSettingsSync(
+ ExtensionBlocklistMLBF._client,
+ lastEntryTimes.addons_mlbf
+ ),
+ ]);
+
+ assertTelemetryScalars({
+ "blocklist.lastModified_rs_addons_mlbf": lastEntryTimesUTC.addons_mlbf,
+ });
+ Assert.equal(
+ new Date(lastEntryTimesUTC.addons_mlbf).getTime(),
+ testGetValue(Glean.blocklist.lastModifiedRsAddonsMblf).getTime()
+ );
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange.js
new file mode 100644
index 0000000000..cd80b34ac0
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange.js
@@ -0,0 +1,1410 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Checks that changes that cause an add-on to become unblocked or blocked have
+// the right effect
+
+// The tests follow a mostly common pattern. First they start with the add-ons
+// unblocked, then they make a change that causes the add-ons to become blocked
+// then they make a similar change that keeps the add-ons blocked then they make
+// a change that unblocks the add-ons. Some tests skip the initial part and
+// start with add-ons detected as blocked.
+
+// softblock1 is enabled/disabled by the blocklist changes so its softDisabled
+// property should always match its userDisabled property
+
+// softblock2 gets manually enabled then disabled after it becomes blocked so
+// its softDisabled property should never become true after that
+
+// softblock3 does the same as softblock2 however it remains disabled
+
+// softblock4 is disabled while unblocked and so should never have softDisabled
+// set to true and stay userDisabled. This add-on is not used in tests that
+// start with add-ons blocked as it would be identical to softblock3
+
+const URI_EXTENSION_BLOCKLIST_DIALOG =
+ "chrome://mozapps/content/extensions/blocklist.xhtml";
+
+// Allow insecure updates
+Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
+
+const IS_ANDROID_WITH_BLOCKLIST_V2 =
+ AppConstants.platform == "android" && !AppConstants.NIGHTLY_BUILD;
+
+// This is the initial value of Blocklist.allowDeprecatedBlocklistV2.
+if (IS_ANDROID_WITH_BLOCKLIST_V2) {
+ // test_blocklistchange_v2.js tests blocklist v2, so we should flip the pref
+ // to enable the v3 blocklist on Android.
+ Assert.ok(
+ _TEST_NAME.includes("test_blocklistchange"),
+ `Expected _TEST_NAME to be test_blocklistchange{,_v2}.js`
+ );
+ if (_TEST_NAME.includes("test_blocklistchange.js")) {
+ Assert.equal(
+ Services.prefs.getBoolPref("extensions.blocklist.useMLBF"),
+ false,
+ "Blocklist v3 disabled by default on Android"
+ );
+ Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true);
+ }
+}
+
+// TODO bug 1649906: strip blocklist v2-specific parts of this test.
+// All specific logic is already covered by other test files, but the tests
+// here trigger the logic via higher-level methods, so it may make sense to
+// keep this file even after the removal of blocklist v2.
+const useMLBF = Services.prefs.getBoolPref(
+ "extensions.blocklist.useMLBF",
+ true
+);
+
+var testserver = createHttpServer({ hosts: ["example.com"] });
+
+function permissionPromptHandler(subject, topic, data) {
+ ok(
+ subject?.wrappedJSObject?.info?.resolve,
+ "Got a permission prompt notification as expected"
+ );
+ subject.wrappedJSObject.info.resolve();
+}
+
+Services.obs.addObserver(
+ permissionPromptHandler,
+ "webextension-permission-prompt"
+);
+
+registerCleanupFunction(() => {
+ Services.obs.removeObserver(
+ permissionPromptHandler,
+ "webextension-permission-prompt"
+ );
+});
+
+const XPIS = {};
+
+const ADDON_IDS = [
+ "softblock1@tests.mozilla.org",
+ "softblock2@tests.mozilla.org",
+ "softblock3@tests.mozilla.org",
+ "softblock4@tests.mozilla.org",
+ "hardblock@tests.mozilla.org",
+ "regexpblock@tests.mozilla.org",
+];
+
+const BLOCK_APP = [
+ {
+ guid: "xpcshell@tests.mozilla.org",
+ maxVersion: "2.*",
+ minVersion: "2",
+ },
+];
+// JEXL filter expression that matches BLOCK_APP.
+const BLOCK_APP_FILTER_EXPRESSION = `env.appinfo.ID == "xpcshell@tests.mozilla.org" && env.appinfo.version >= "2" && env.appinfo.version < "3"`;
+
+function softBlockApp(id) {
+ return {
+ guid: `${id}@tests.mozilla.org`,
+ versionRange: [
+ {
+ severity: "1",
+ targetApplication: BLOCK_APP,
+ },
+ ],
+ };
+}
+
+function softBlockAddonChange(id) {
+ return {
+ guid: `${id}@tests.mozilla.org`,
+ versionRange: [
+ {
+ severity: "1",
+ minVersion: "2",
+ maxVersion: "3",
+ },
+ ],
+ };
+}
+
+function softBlockUpdate2(id) {
+ return {
+ guid: `${id}@tests.mozilla.org`,
+ versionRange: [{ severity: "1" }],
+ };
+}
+
+function softBlockManual(id) {
+ return {
+ guid: `${id}@tests.mozilla.org`,
+ versionRange: [
+ {
+ maxVersion: "2",
+ minVersion: "1",
+ severity: "1",
+ },
+ ],
+ };
+}
+
+const BLOCKLIST_DATA = {
+ empty_blocklist: [],
+ app_update: [
+ softBlockApp("softblock1"),
+ softBlockApp("softblock2"),
+ softBlockApp("softblock3"),
+ softBlockApp("softblock4"),
+ {
+ guid: "hardblock@tests.mozilla.org",
+ versionRange: [
+ {
+ targetApplication: BLOCK_APP,
+ },
+ ],
+ },
+ {
+ guid: "/^RegExp/",
+ versionRange: [
+ {
+ severity: "1",
+ targetApplication: BLOCK_APP,
+ },
+ ],
+ },
+ {
+ guid: "/^RegExp/i",
+ versionRange: [
+ {
+ targetApplication: BLOCK_APP,
+ },
+ ],
+ },
+ ],
+ addon_change: [
+ softBlockAddonChange("softblock1"),
+ softBlockAddonChange("softblock2"),
+ softBlockAddonChange("softblock3"),
+ softBlockAddonChange("softblock4"),
+ {
+ guid: "hardblock@tests.mozilla.org",
+ versionRange: [
+ {
+ maxVersion: "3",
+ minVersion: "2",
+ },
+ ],
+ },
+ {
+ _comment:
+ "Two RegExp matches, so test flags work - first shouldn't match.",
+ guid: "/^RegExp/",
+ versionRange: [
+ {
+ maxVersion: "3",
+ minVersion: "2",
+ severity: "1",
+ },
+ ],
+ },
+ {
+ guid: "/^RegExp/i",
+ versionRange: [
+ {
+ maxVersion: "3",
+ minVersion: "2",
+ severity: "2",
+ },
+ ],
+ },
+ ],
+ blocklist_update2: [
+ softBlockUpdate2("softblock1"),
+ softBlockUpdate2("softblock2"),
+ softBlockUpdate2("softblock3"),
+ softBlockUpdate2("softblock4"),
+ {
+ guid: "hardblock@tests.mozilla.org",
+ versionRange: [],
+ },
+ {
+ guid: "/^RegExp/",
+ versionRange: [{ severity: "1" }],
+ },
+ {
+ guid: "/^RegExp/i",
+ versionRange: [],
+ },
+ ],
+ manual_update: [
+ softBlockManual("softblock1"),
+ softBlockManual("softblock2"),
+ softBlockManual("softblock3"),
+ softBlockManual("softblock4"),
+ {
+ guid: "hardblock@tests.mozilla.org",
+ versionRange: [
+ {
+ maxVersion: "2",
+ minVersion: "1",
+ },
+ ],
+ },
+ {
+ guid: "/^RegExp/i",
+ versionRange: [
+ {
+ maxVersion: "2",
+ minVersion: "1",
+ },
+ ],
+ },
+ ],
+};
+
+// Blocklist v3 (useMLBF) only supports hard blocks by guid+version. Version
+// ranges, regexps and soft blocks are not supported. So adjust expectations to
+// ensure that the test passes even if useMLBF=true, by:
+// - soft blocks are converted to hard blocks.
+// - hard blocks are accepted as-is.
+// - regexps blocks are converted to hard blocks.
+// - Version ranges are expanded to cover all known versions.
+if (useMLBF) {
+ for (let [key, blocks] of Object.entries(BLOCKLIST_DATA)) {
+ BLOCKLIST_DATA[key] = [];
+ for (let block of blocks) {
+ let { guid } = block;
+ if (guid.includes("RegExp")) {
+ guid = "regexpblock@tests.mozilla.org";
+ } else if (!guid.startsWith("soft") && !guid.startsWith("hard")) {
+ throw new Error(`Unexpected mock addon ID: ${guid}`);
+ }
+
+ const {
+ minVersion = "1",
+ maxVersion = "3",
+ targetApplication,
+ } = block.versionRange?.[0] || {};
+
+ for (let v = minVersion; v <= maxVersion; ++v) {
+ BLOCKLIST_DATA[key].push({
+ // Assume that IF targetApplication is set, that it is BLOCK_APP.
+ filter_expression: targetApplication && BLOCK_APP_FILTER_EXPRESSION,
+ stash: {
+ // XPI files use version `${v}.0`, update manifests use `${v}`.
+ blocked: [`${guid}:${v}.0`, `${guid}:${v}`],
+ unblocked: [],
+ },
+ });
+ }
+ }
+ }
+}
+
+// XXXgijs: according to https://bugzilla.mozilla.org/show_bug.cgi?id=1257565#c111
+// this code and the related code in Blocklist.jsm (specific to XML blocklist) is
+// dead code and can be removed. See https://bugzilla.mozilla.org/show_bug.cgi?id=1549550 .
+//
+// Don't need the full interface, attempts to call other methods will just
+// throw which is just fine
+var WindowWatcher = {
+ openWindow(parent, url, name, features, openArgs) {
+ // Should be called to list the newly blocklisted items
+ Assert.equal(url, URI_EXTENSION_BLOCKLIST_DIALOG);
+
+ // Simulate auto-disabling any softblocks
+ var list = openArgs.wrappedJSObject.list;
+ list.forEach(function (aItem) {
+ if (!aItem.blocked) {
+ aItem.disable = true;
+ }
+ });
+
+ // run the code after the blocklist is closed
+ Services.obs.notifyObservers(null, "addon-blocklist-closed");
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIWindowWatcher"]),
+};
+
+MockRegistrar.register(
+ "@mozilla.org/embedcomp/window-watcher;1",
+ WindowWatcher
+);
+
+var InstallConfirm = {
+ confirm(aWindow, aUrl, aInstalls) {
+ aInstalls.forEach(function (aInstall) {
+ aInstall.install();
+ });
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["amIWebInstallPrompt"]),
+};
+
+var InstallConfirmFactory = {
+ createInstance: function createInstance(iid) {
+ return InstallConfirm.QueryInterface(iid);
+ },
+};
+
+var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+registrar.registerFactory(
+ Components.ID("{f0863905-4dde-42e2-991c-2dc8209bc9ca}"),
+ "Fake Install Prompt",
+ "@mozilla.org/addons/web-install-prompt;1",
+ InstallConfirmFactory
+);
+
+function Pload_blocklist(aId) {
+ return AddonTestUtils.loadBlocklistRawData({
+ [useMLBF ? "extensionsMLBF" : "extensions"]: BLOCKLIST_DATA[aId],
+ });
+}
+
+// Does a background update check for add-ons and returns a promise that
+// resolves when any started installs complete
+function Pbackground_update() {
+ return new Promise((resolve, reject) => {
+ let installCount = 0;
+ let backgroundCheckCompleted = false;
+
+ AddonManager.addInstallListener({
+ onNewInstall(aInstall) {
+ installCount++;
+ },
+
+ onInstallEnded(aInstall) {
+ installCount--;
+ // Wait until all started installs have completed
+ if (installCount) {
+ return;
+ }
+
+ AddonManager.removeInstallListener(this);
+
+ // If the background check hasn't yet completed then let that call the
+ // callback when it is done
+ if (!backgroundCheckCompleted) {
+ return;
+ }
+
+ resolve();
+ },
+ });
+
+ Services.obs.addObserver(function observer() {
+ Services.obs.removeObserver(
+ observer,
+ "addons-background-update-complete"
+ );
+ backgroundCheckCompleted = true;
+
+ // If any new installs have started then we'll call the callback once they
+ // are completed
+ if (installCount) {
+ return;
+ }
+
+ resolve();
+ }, "addons-background-update-complete");
+
+ AddonManagerPrivate.backgroundUpdateCheck();
+ });
+}
+
+// Manually updates the test add-ons to the given version
+function Pmanual_update(aVersion) {
+ const names = ["soft1", "soft2", "soft3", "soft4", "hard", "regexp"];
+ return Promise.all(
+ names.map(async name => {
+ let url = `http://example.com/addons/blocklist_${name}_${aVersion}.xpi`;
+ let install = await AddonManager.getInstallForURL(url);
+
+ // installAddonFromAOM() does more checking than install.install().
+ // In particular, it will refuse to install an incompatible addon.
+
+ return new Promise(resolve => {
+ install.addListener({
+ onDownloadCancelled: resolve,
+ onInstallEnded: resolve,
+ });
+
+ AddonManager.installAddonFromAOM(null, null, install);
+ });
+ })
+ );
+}
+
+// Checks that an add-ons properties match expected values
+function check_addon(
+ aAddon,
+ aExpectedVersion,
+ aExpectedUserDisabled,
+ aExpectedSoftDisabled,
+ aExpectedState
+) {
+ if (useMLBF) {
+ if (aAddon.id.startsWith("soft")) {
+ if (aExpectedState === Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
+ // The whole test file assumes that an add-on is "user-disabled" after
+ // an explicit disable(), or after a soft block (without enable()).
+ // With useMLBF, soft blocks are not supported, so the "user-disabled"
+ // state matches the usual behavior of "userDisabled" (=disable()).
+ aExpectedUserDisabled = aAddon.userDisabled;
+ aExpectedSoftDisabled = false;
+ aExpectedState = Ci.nsIBlocklistService.STATE_BLOCKED;
+ }
+ }
+ }
+
+ Assert.notEqual(aAddon, null);
+ info(
+ "Testing " +
+ aAddon.id +
+ " version " +
+ aAddon.version +
+ " user " +
+ aAddon.userDisabled +
+ " soft " +
+ aAddon.softDisabled +
+ " perms " +
+ aAddon.permissions
+ );
+
+ Assert.equal(aAddon.version, aExpectedVersion);
+ Assert.equal(aAddon.blocklistState, aExpectedState);
+ Assert.equal(aAddon.userDisabled, aExpectedUserDisabled);
+ Assert.equal(aAddon.softDisabled, aExpectedSoftDisabled);
+ if (aAddon.softDisabled) {
+ Assert.ok(aAddon.userDisabled);
+ }
+
+ if (aExpectedState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ info("blocked, PERM_CAN_ENABLE " + aAddon.id);
+ Assert.ok(!hasFlag(aAddon.permissions, AddonManager.PERM_CAN_ENABLE));
+ info("blocked, PERM_CAN_DISABLE " + aAddon.id);
+ Assert.ok(!hasFlag(aAddon.permissions, AddonManager.PERM_CAN_DISABLE));
+ } else if (aAddon.userDisabled) {
+ info("userDisabled, PERM_CAN_ENABLE " + aAddon.id);
+ Assert.ok(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_ENABLE));
+ info("userDisabled, PERM_CAN_DISABLE " + aAddon.id);
+ Assert.ok(!hasFlag(aAddon.permissions, AddonManager.PERM_CAN_DISABLE));
+ } else {
+ info("other, PERM_CAN_ENABLE " + aAddon.id);
+ Assert.ok(!hasFlag(aAddon.permissions, AddonManager.PERM_CAN_ENABLE));
+ if (aAddon.type != "theme") {
+ info("other, PERM_CAN_DISABLE " + aAddon.id);
+ Assert.ok(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_DISABLE));
+ }
+ }
+ Assert.equal(
+ aAddon.appDisabled,
+ aExpectedState == Ci.nsIBlocklistService.STATE_BLOCKED
+ );
+
+ let willBeActive = aAddon.isActive;
+ if (hasFlag(aAddon.pendingOperations, AddonManager.PENDING_DISABLE)) {
+ willBeActive = false;
+ } else if (hasFlag(aAddon.pendingOperations, AddonManager.PENDING_ENABLE)) {
+ willBeActive = true;
+ }
+
+ if (
+ aExpectedUserDisabled ||
+ aExpectedState == Ci.nsIBlocklistService.STATE_BLOCKED
+ ) {
+ Assert.ok(!willBeActive);
+ } else {
+ Assert.ok(willBeActive);
+ }
+}
+
+async function promiseRestartManagerWithAppChange(version) {
+ await promiseShutdownManager();
+ await promiseStartupManagerWithAppChange(version);
+}
+
+async function promiseStartupManagerWithAppChange(version) {
+ if (version) {
+ AddonTestUtils.appInfo.version = version;
+ }
+ if (useMLBF) {
+ // The old ExtensionBlocklist enforced the app version/ID part of the block
+ // when the blocklist entry is checked.
+ // The new ExtensionBlocklist (with useMLBF=true) does not separately check
+ // the app version/ID, but the underlying data source (Remote Settings)
+ // does offer the ability to filter entries with `filter_expression`.
+ // Force a reload to ensure that the BLOCK_APP_FILTER_EXPRESSION filter in
+ // this test file is checked again against the new version.
+ await Blocklist.ExtensionBlocklist._updateMLBF();
+ }
+ await promiseStartupManager();
+}
+
+add_task(async function setup() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+ if (useMLBF) {
+ const { ClientEnvironmentBase } = ChromeUtils.importESModule(
+ "resource://gre/modules/components-utils/ClientEnvironment.sys.mjs"
+ );
+ Object.defineProperty(ClientEnvironmentBase, "appinfo", {
+ configurable: true,
+ get() {
+ return gAppInfo;
+ },
+ });
+ }
+
+ function getxpibasename(id, version) {
+ // pattern used to map ids like softblock1 to soft1
+ let pattern = /^(soft|hard|regexp)block([1-9]*)@/;
+ let match = id.match(pattern);
+ return `blocklist_${match[1]}${match[2]}_${version}`;
+ }
+ for (let id of ADDON_IDS) {
+ for (let version of [1, 2, 3, 4]) {
+ let name = getxpibasename(id, version);
+
+ let xpi = createTempWebExtensionFile({
+ manifest: {
+ name: "Test",
+ version: `${version}.0`,
+ browser_specific_settings: {
+ gecko: {
+ id,
+ // This file is generated below, as updateJson.
+ update_url: `http://example.com/addon_update${version}.json`,
+ },
+ },
+ },
+ });
+
+ // To test updates, individual tasks in this test file start the test by
+ // installing a set of add-ons with version |version| and trigger an
+ // update check, from XPIS.${nameprefix}${version} (version = 1, 2, 3)
+ if (version != 4) {
+ XPIS[name] = xpi;
+ }
+
+ // update_url above points to a test manifest that references the next
+ // version. The xpi is made available on the server, so that the test
+ // can verify that the blocklist works as intended (i.e. update to newer
+ // version is blocked).
+ // There is nothing that updates to version 1, only to versions 2, 3, 4.
+ if (version != 1) {
+ testserver.registerFile(`/addons/${name}.xpi`, xpi);
+ }
+ }
+ }
+
+ // For each version that this test file uses, create a test manifest that
+ // references the next version for each id in ADDON_IDS.
+ for (let version of [1, 2, 3]) {
+ let updateJson = { addons: {} };
+ for (let id of ADDON_IDS) {
+ let nextversion = version + 1;
+ let name = getxpibasename(id, nextversion);
+ updateJson.addons[id] = {
+ updates: [
+ {
+ applications: {
+ gecko: {
+ strict_min_version: "0",
+ advisory_max_version: "*",
+ },
+ },
+ version: `${nextversion}.0`,
+ update_link: `http://example.com/addons/${name}.xpi`,
+ },
+ ],
+ };
+ }
+ AddonTestUtils.registerJSON(
+ testserver,
+ `/addon_update${version}.json`,
+ updateJson
+ );
+ }
+
+ await promiseStartupManager();
+
+ await promiseInstallFile(XPIS.blocklist_soft1_1);
+ await promiseInstallFile(XPIS.blocklist_soft2_1);
+ await promiseInstallFile(XPIS.blocklist_soft3_1);
+ await promiseInstallFile(XPIS.blocklist_soft4_1);
+ await promiseInstallFile(XPIS.blocklist_hard_1);
+ await promiseInstallFile(XPIS.blocklist_regexp_1);
+
+ let s4 = await promiseAddonByID("softblock4@tests.mozilla.org");
+ await s4.disable();
+});
+
+// Starts with add-ons unblocked and then switches application versions to
+// change add-ons to blocked and back
+add_task(async function run_app_update_test() {
+ await Pload_blocklist("app_update");
+ await promiseRestartManager();
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(
+ s2,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+});
+
+add_task(async function app_update_step_2() {
+ await promiseRestartManagerWithAppChange("2");
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+
+ await s2.enable();
+ await s2.disable();
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ await s3.enable();
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+ );
+});
+
+add_task(async function app_update_step_3() {
+ await promiseRestartManager();
+
+ await promiseRestartManagerWithAppChange("2.5");
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+});
+
+add_task(async function app_update_step_4() {
+ await promiseRestartManagerWithAppChange("1");
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+
+ await s1.enable();
+ await s2.enable();
+});
+
+// Starts with add-ons unblocked and then switches application versions to
+// change add-ons to blocked and back. A DB schema change is faked to force a
+// rebuild when the application version changes
+add_task(async function run_app_update_schema_test() {
+ await promiseRestartManager();
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(
+ s2,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+});
+
+add_task(async function update_schema_2() {
+ await promiseShutdownManager();
+
+ await changeXPIDBVersion(100);
+ gAppInfo.version = "2";
+ await promiseStartupManagerWithAppChange();
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+
+ await s2.enable();
+ await s2.disable();
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ await s3.enable();
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+ );
+});
+
+add_task(async function update_schema_3() {
+ await promiseRestartManager();
+
+ await promiseShutdownManager();
+ await changeXPIDBVersion(100);
+ gAppInfo.version = "2.5";
+ await promiseStartupManagerWithAppChange();
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+});
+
+add_task(async function update_schema_4() {
+ await promiseShutdownManager();
+
+ await changeXPIDBVersion(100);
+ await promiseStartupManager();
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+});
+
+add_task(async function update_schema_5() {
+ await promiseShutdownManager();
+
+ await changeXPIDBVersion(100);
+ gAppInfo.version = "1";
+ await promiseStartupManagerWithAppChange();
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+
+ await s1.enable();
+ await s2.enable();
+});
+
+// Starts with add-ons unblocked and then loads new blocklists to change add-ons
+// to blocked and back again.
+add_task(async function run_blocklist_update_test() {
+ await Pload_blocklist("empty_blocklist");
+ await promiseRestartManager();
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(
+ s2,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+
+ await Pload_blocklist("blocklist_update2");
+ await promiseRestartManager();
+
+ [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+
+ await s2.enable();
+ await s2.disable();
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ await s3.enable();
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+ );
+
+ await promiseRestartManager();
+
+ await Pload_blocklist("blocklist_update2");
+ await promiseRestartManager();
+
+ [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+
+ await Pload_blocklist("empty_blocklist");
+ await promiseRestartManager();
+
+ [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+
+ await s1.enable();
+ await s2.enable();
+});
+
+// Starts with add-ons unblocked and then new versions are installed outside of
+// the app to change them to blocked and back again.
+add_task(async function run_addon_change_test() {
+ await Pload_blocklist("addon_change");
+ await promiseRestartManager();
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(
+ s2,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+});
+
+add_task(async function run_addon_change_2() {
+ await promiseInstallFile(XPIS.blocklist_soft1_2);
+ await promiseInstallFile(XPIS.blocklist_soft2_2);
+ await promiseInstallFile(XPIS.blocklist_soft3_2);
+ await promiseInstallFile(XPIS.blocklist_soft4_2);
+ await promiseInstallFile(XPIS.blocklist_hard_2);
+ await promiseInstallFile(XPIS.blocklist_regexp_2);
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s3, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s4, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "2.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "2.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+
+ await s2.enable();
+ await s2.disable();
+ check_addon(s2, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ await s3.enable();
+ check_addon(
+ s3,
+ "2.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+ );
+});
+
+add_task(async function run_addon_change_3() {
+ await promiseInstallFile(XPIS.blocklist_soft1_3);
+ await promiseInstallFile(XPIS.blocklist_soft2_3);
+ await promiseInstallFile(XPIS.blocklist_soft3_3);
+ await promiseInstallFile(XPIS.blocklist_soft4_3);
+ await promiseInstallFile(XPIS.blocklist_hard_3);
+ await promiseInstallFile(XPIS.blocklist_regexp_3);
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "3.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(
+ s3,
+ "3.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+ );
+ check_addon(s4, "3.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "3.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+});
+
+add_task(async function run_addon_change_4() {
+ await promiseInstallFile(XPIS.blocklist_soft1_1);
+ await promiseInstallFile(XPIS.blocklist_soft2_1);
+ await promiseInstallFile(XPIS.blocklist_soft3_1);
+ await promiseInstallFile(XPIS.blocklist_soft4_1);
+ await promiseInstallFile(XPIS.blocklist_hard_1);
+ await promiseInstallFile(XPIS.blocklist_regexp_1);
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+
+ await s1.enable();
+ await s2.enable();
+});
+
+// Add-ons are initially unblocked then attempts to upgrade to blocked versions
+// in the background which should fail
+add_task(async function run_background_update_test() {
+ await promiseRestartManager();
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(
+ s2,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+
+ await Pbackground_update();
+ await promiseRestartManager();
+
+ [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(
+ s2,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+});
+
+// Starts with add-ons blocked and then new versions are detected and installed
+// automatically for unblocked versions.
+add_task(async function run_background_update_2_test() {
+ await promiseInstallFile(XPIS.blocklist_soft1_3);
+ await promiseInstallFile(XPIS.blocklist_soft2_3);
+ await promiseInstallFile(XPIS.blocklist_soft3_3);
+ await promiseInstallFile(XPIS.blocklist_soft4_3);
+ await promiseInstallFile(XPIS.blocklist_hard_3);
+ await promiseInstallFile(XPIS.blocklist_regexp_3);
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "3.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "3.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s3, "3.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "3.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+
+ await s2.enable();
+ await s2.disable();
+ check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ await s3.enable();
+ check_addon(
+ s3,
+ "3.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+ );
+
+ await Pbackground_update();
+
+ [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "4.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s2, "4.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(
+ s3,
+ "4.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(h, "4.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "4.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+
+ await s1.enable();
+ await s2.enable();
+ await s4.disable();
+});
+
+// The next test task (run_manual_update_test) was written to expect version 1,
+// but after the previous test, version 4 of the add-ons were installed.
+add_task(async function reset_addons_to_version_1_instead_of_4() {
+ await promiseInstallFile(XPIS.blocklist_soft1_1);
+ await promiseInstallFile(XPIS.blocklist_soft2_1);
+ await promiseInstallFile(XPIS.blocklist_soft3_1);
+ await promiseInstallFile(XPIS.blocklist_soft4_1);
+ await promiseInstallFile(XPIS.blocklist_hard_1);
+ await promiseInstallFile(XPIS.blocklist_regexp_1);
+});
+
+// Starts with add-ons blocked and then simulates the user upgrading them to
+// unblocked versions.
+add_task(async function run_manual_update_test() {
+ await Pload_blocklist("manual_update");
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+
+ await s2.enable();
+ await s2.disable();
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ await s3.enable();
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+ );
+
+ await Pmanual_update("2");
+
+ [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ // With useMLBF, s1/s2/s3 are hard blocks, so they cannot update.
+ const sv2 = useMLBF ? "1.0" : "2.0";
+ check_addon(s1, sv2, true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, sv2, true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s3, sv2, false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s4, sv2, true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ // Can't manually update to a hardblocked add-on
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+
+ await Pmanual_update("3");
+
+ [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "3.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(
+ s3,
+ "3.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s4, "3.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(h, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+});
+
+// Starts with add-ons blocked and then new versions are installed outside of
+// the app to change them to unblocked.
+add_task(async function run_manual_update_2_test() {
+ let addons = await promiseAddonsByIDs(ADDON_IDS);
+ await Promise.all(addons.map(addon => addon.uninstall()));
+
+ await promiseInstallFile(XPIS.blocklist_soft1_1);
+ await promiseInstallFile(XPIS.blocklist_soft2_1);
+ await promiseInstallFile(XPIS.blocklist_soft3_1);
+ await promiseInstallFile(XPIS.blocklist_soft4_1);
+ await promiseInstallFile(XPIS.blocklist_hard_1);
+ await promiseInstallFile(XPIS.blocklist_regexp_1);
+
+ let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+
+ await s2.enable();
+ await s2.disable();
+ check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ await s3.enable();
+ check_addon(
+ s3,
+ "1.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_SOFTBLOCKED
+ );
+
+ await Pmanual_update("2");
+
+ [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ // With useMLBF, s1/s2/s3 are hard blocks, so they cannot update.
+ const sv2 = useMLBF ? "1.0" : "2.0";
+ check_addon(s1, sv2, true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, sv2, true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s3, sv2, false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ // Can't manually update to a hardblocked add-on
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+
+ await Pmanual_update("3");
+
+ [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(
+ s1,
+ "3.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(
+ s3,
+ "3.0",
+ false,
+ false,
+ Ci.nsIBlocklistService.STATE_NOT_BLOCKED
+ );
+ check_addon(h, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+
+ await s1.enable();
+ await s2.enable();
+ await s4.disable();
+});
+
+// Uses the API to install blocked add-ons from the local filesystem
+add_task(async function run_local_install_test() {
+ let addons = await promiseAddonsByIDs(ADDON_IDS);
+ await Promise.all(addons.map(addon => addon.uninstall()));
+
+ await promiseInstallAllFiles([
+ XPIS.blocklist_soft1_1,
+ XPIS.blocklist_soft2_1,
+ XPIS.blocklist_soft3_1,
+ XPIS.blocklist_soft4_1,
+ XPIS.blocklist_hard_1,
+ XPIS.blocklist_regexp_1,
+ ]);
+
+ let installs = await AddonManager.getAllInstalls();
+ // Should have finished all installs without needing to restart
+ Assert.equal(installs.length, 0);
+
+ let [s1, s2, s3 /* s4 */, , h, r] = await promiseAddonsByIDs(ADDON_IDS);
+
+ check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+ check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange_v2.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange_v2.js
new file mode 100644
index 0000000000..d884438def
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_blocklistchange_v2.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// useMLBF=true doesn't support soft blocks, regexps or version ranges.
+// Flip the useMLBF preference to make sure that the test_blocklistchange.js
+// test works with and without this pref (blocklist v2 and blocklist v3).
+enable_blocklist_v2_instead_of_useMLBF();
+
+Services.scriptloader.loadSubScript(
+ Services.io.newFileURI(do_get_file("test_blocklistchange.js")).spec,
+ this
+);
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Device.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Device.js
new file mode 100644
index 0000000000..9b1d84b77d
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Device.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether a machine which differs only on device ID, but otherwise
+// exactly matches the blacklist entry, is not blocked.
+// Uses test_gfxBlacklist.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x9876");
+ gfxInfo.spoofDriverVersion("8.52.322.2201");
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x9876");
+ break;
+ case "Darwin":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x9876");
+ gfxInfo.spoofOSVersion(0xa0900);
+ break;
+ case "Android":
+ gfxInfo.spoofVendorID("abcd");
+ gfxInfo.spoofDeviceID("aabb");
+ gfxInfo.spoofDriverVersion("5");
+ break;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_CANVAS2D_ACCELERATION
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_DriverNew.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_DriverNew.js
new file mode 100644
index 0000000000..a1bcde5566
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_DriverNew.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether a new-enough driver bypasses the blacklist, even if the rest of
+// the attributes match the blacklist entry.
+// Uses test_gfxBlacklist.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofDriverVersion("8.52.322.2202");
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ // We don't support driver versions on Linux.
+ do_test_finished();
+ return;
+ case "Darwin":
+ // We don't support driver versions on Darwin.
+ do_test_finished();
+ return;
+ case "Android":
+ gfxInfo.spoofVendorID("abcd");
+ gfxInfo.spoofDeviceID("wxyz");
+ gfxInfo.spoofDriverVersion("6");
+ break;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverNew.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverNew.js
new file mode 100644
index 0000000000..ec74d813ae
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverNew.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether a machine which is newer than the equal
+// blacklist entry is allowed.
+// Uses test_gfxBlacklist.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ gfxInfo.spoofVendorID("0xdcdc");
+ gfxInfo.spoofDeviceID("0x1234");
+ // test_gfxBlacklist.json has several entries targeting "os": "All"
+ // ("All" meaning "All Windows"), with several combinations of
+ // "driverVersion" / "driverVersionMax" / "driverVersionComparator".
+ gfxInfo.spoofDriverVersion("8.52.322.1112");
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ // We don't support driver versions on Linux.
+ // XXX don't we? Seems like we do since bug 1294232 with the change in
+ // https://hg.mozilla.org/mozilla-central/diff/8962b8d9b7a6/widget/GfxInfoBase.cpp
+ // To update this test, we'd have to update test_gfxBlacklist.json in a
+ // way similar to how bug 1714673 was resolved for Android.
+ do_test_finished();
+ return;
+ case "Darwin":
+ // We don't support driver versions on OS X.
+ do_test_finished();
+ return;
+ case "Android":
+ gfxInfo.spoofVendorID("dcdc");
+ gfxInfo.spoofDeviceID("uiop");
+ gfxInfo.spoofDriverVersion("6");
+ break;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "15.0", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ // Make sure unrelated features aren't affected
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_11_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_OPENGL_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_11_ANGLE);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_HARDWARE_VIDEO_DECODING
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION_H264
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION_DECODE
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION_ENCODE
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBGL_ANGLE);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_CANVAS2D_ACCELERATION
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverOld.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverOld.js
new file mode 100644
index 0000000000..ff887a92eb
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverOld.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether a machine which is older than the equal
+// blacklist entry is correctly allowed.
+// Uses test_gfxBlacklist.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ gfxInfo.spoofVendorID("0xdcdc");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofDriverVersion("8.52.322.1110");
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ // We don't support driver versions on Linux.
+ do_test_finished();
+ return;
+ case "Darwin":
+ // We don't support driver versions on Darwin.
+ do_test_finished();
+ return;
+ case "Android":
+ gfxInfo.spoofVendorID("dcdc");
+ gfxInfo.spoofDeviceID("uiop");
+ gfxInfo.spoofDriverVersion("4");
+ break;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ // Make sure unrelated features aren't affected
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_OK.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_OK.js
new file mode 100644
index 0000000000..1eef119663
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_OK.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether a machine which exactly matches the equal
+// blacklist entry is successfully blocked.
+// Uses test_gfxBlacklist.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ gfxInfo.spoofVendorID("0xdcdc");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofDriverVersion("8.52.322.1111");
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ // We don't support driver versions on Linux.
+ do_test_finished();
+ return;
+ case "Darwin":
+ // We don't support driver versions on Darwin.
+ do_test_finished();
+ return;
+ case "Android":
+ gfxInfo.spoofVendorID("dcdc");
+ gfxInfo.spoofDeviceID("uiop");
+ gfxInfo.spoofDriverVersion("5");
+ break;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+
+ // Make sure unrelated features aren't affected
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_DriverOld.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_DriverOld.js
new file mode 100644
index 0000000000..182c825ffb
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_DriverOld.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether a machine which is lower than the greater-than-or-equal
+// blacklist entry is allowed.
+// Uses test_gfxBlacklist.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ gfxInfo.spoofVendorID("0xabab");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofDriverVersion("8.52.322.2201");
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ // We don't support driver versions on Linux.
+ do_test_finished();
+ return;
+ case "Darwin":
+ // We don't support driver versions on Darwin.
+ do_test_finished();
+ return;
+ case "Android":
+ gfxInfo.spoofVendorID("abab");
+ gfxInfo.spoofDeviceID("ghjk");
+ gfxInfo.spoofDriverVersion("6");
+ break;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ // Make sure unrelated features aren't affected
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_OK.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_OK.js
new file mode 100644
index 0000000000..2cc3686007
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_OK.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether a machine which exactly matches the greater-than-or-equal
+// blacklist entry is successfully blocked.
+// Uses test_gfxBlacklist.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ gfxInfo.spoofVendorID("0xabab");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofDriverVersion("8.52.322.2202");
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ // We don't support driver versions on Linux.
+ // XXX don't we? Seems like we do since bug 1294232 with the change in
+ // https://hg.mozilla.org/mozilla-central/diff/8962b8d9b7a6/widget/GfxInfoBase.cpp
+ do_test_finished();
+ return;
+ case "Darwin":
+ // We don't support driver versions on Darwin.
+ do_test_finished();
+ return;
+ case "Android":
+ gfxInfo.spoofVendorID("abab");
+ gfxInfo.spoofDeviceID("ghjk");
+ gfxInfo.spoofDriverVersion("7");
+ break;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+
+ // Make sure unrelated features aren't affected
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_No_Comparison.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_No_Comparison.js
new file mode 100644
index 0000000000..169cdc5e62
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_No_Comparison.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether a machine which exactly matches the blacklist entry is
+// successfully blocked.
+// Uses test_gfxBlacklist.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x6666");
+
+ // Spoof the OS version so it matches the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ break;
+ case "Darwin":
+ gfxInfo.spoofOSVersion(0xa0900);
+ break;
+ case "Android":
+ break;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ var driverVersion = gfxInfo.adapterDriverVersion;
+ if (driverVersion) {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE);
+
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBRENDER);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE);
+
+ // Make sure unrelated features aren't affected
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+ }
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OK.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OK.js
new file mode 100644
index 0000000000..04d766e027
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OK.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether a machine which exactly matches the blacklist entry is
+// successfully blocked.
+// Uses test_gfxBlacklist.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofDriverVersion("8.52.322.2201");
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ break;
+ case "Darwin":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofOSVersion(0xa0900);
+ break;
+ case "Android":
+ gfxInfo.spoofVendorID("abcd");
+ gfxInfo.spoofDeviceID("asdf");
+ gfxInfo.spoofDriverVersion("5");
+ break;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+
+ // Make sure unrelated features aren't affected
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OS.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OS.js
new file mode 100644
index 0000000000..ce5a61cb75
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OS.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether a machine which differs only on OS version, but otherwise
+// exactly matches the blacklist entry, is not blocked.
+// Uses test_gfxBlacklist.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofDriverVersion("8.52.322.2201");
+ // Windows Vista
+ gfxInfo.spoofOSVersion(0x60000);
+ break;
+ case "Linux":
+ // We don't have any OS versions on Linux, just "Linux".
+ do_test_finished();
+ return;
+ case "Darwin":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofOSVersion(0xa0800);
+ break;
+ case "Android":
+ // On Android, the driver version is used as the OS version (because
+ // there's so many of them).
+ do_test_finished();
+ return;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_match.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_match.js
new file mode 100644
index 0000000000..7a4ec276ee
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_match.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether new OS versions are matched properly.
+// Uses test_gfxBlacklist_OSVersion.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ gfxInfo.spoofDriverVersion("8.52.322.2201");
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+
+ // Spoof the version of the OS appropriately to test the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ // Windows 8
+ gfxInfo.spoofOSVersion(0x60002);
+ break;
+ case "Linux":
+ // We don't have any OS versions on Linux, just "Linux".
+ do_test_finished();
+ return;
+ case "Darwin":
+ // Mountain Lion
+ gfxInfo.spoofOSVersion(0xa0900);
+ break;
+ case "Android":
+ // On Android, the driver version is used as the OS version (because
+ // there's so many of them).
+ do_test_finished();
+ return;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ if (Services.appinfo.OS == "WINNT") {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+ } else if (Services.appinfo.OS == "Darwin") {
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_OPENGL_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+ }
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist_OSVersion.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js
new file mode 100644
index 0000000000..61dba8db96
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether blocklists specifying new OSes correctly don't block if driver
+// versions are appropriately up-to-date.
+// Uses test_gfxBlacklist_OSVersion.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ gfxInfo.spoofDriverVersion("8.52.322.2202");
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+
+ // Spoof the version of the OS appropriately to test the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ // Windows 8
+ gfxInfo.spoofOSVersion(0x60002);
+ break;
+ case "Linux":
+ // We don't have any OS versions on Linux, just "Linux".
+ do_test_finished();
+ return;
+ case "Darwin":
+ gfxInfo.spoofOSVersion(0xa0800);
+ break;
+ case "Android":
+ // On Android, the driver version is used as the OS version (because
+ // there's so many of them).
+ do_test_finished();
+ return;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ if (Services.appinfo.OS == "WINNT") {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+ } else if (Services.appinfo.OS == "Darwin") {
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_OPENGL_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+ }
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist_OSVersion.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js
new file mode 100644
index 0000000000..117e2a34ee
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether old OS versions are not matched when the blacklist contains
+// only new OS versions.
+// Uses test_gfxBlacklist_OSVersion.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ gfxInfo.spoofDriverVersion("8.52.322.2201");
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+
+ // Spoof the version of the OS appropriately to test the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ // We don't have any OS versions on Linux, just "Linux".
+ do_test_finished();
+ return;
+ case "Darwin":
+ // Lion
+ gfxInfo.spoofOSVersion(0xa0800);
+ break;
+ case "Android":
+ // On Android, the driver version is used as the OS version (because
+ // there's so many of them).
+ do_test_finished();
+ return;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ if (Services.appinfo.OS == "WINNT") {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+ } else if (Services.appinfo.OS == "Darwin") {
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_OPENGL_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+ }
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist_OSVersion.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Vendor.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Vendor.js
new file mode 100644
index 0000000000..37bc0d3c89
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Vendor.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether a machine which differs only on vendor, but otherwise
+// exactly matches the blacklist entry, is not blocked.
+// Uses test_gfxBlacklist.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ gfxInfo.spoofVendorID("0xdcba");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofDriverVersion("8.52.322.2201");
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ gfxInfo.spoofVendorID("0xdcba");
+ gfxInfo.spoofDeviceID("0x1234");
+ break;
+ case "Darwin":
+ gfxInfo.spoofVendorID("0xdcba");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofOSVersion(0xa0900);
+ break;
+ case "Android":
+ gfxInfo.spoofVendorID("dcba");
+ gfxInfo.spoofDeviceID("asdf");
+ gfxInfo.spoofDriverVersion("5");
+ break;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function checkBlacklist() {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlacklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Version.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Version.js
new file mode 100644
index 0000000000..9a6a904465
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Version.js
@@ -0,0 +1,190 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether a machine which exactly matches the blocklist entry is
+// successfully blocked.
+// Uses test_gfxBlacklist_AllOS.json
+
+// Performs the initial setup
+async function run_test() {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Save OS in variable since createAppInfo below will change it to "xpcshell".
+ const OS = Services.appinfo.OS;
+ // Set the vendor/device ID, etc, to match the test file.
+ switch (OS) {
+ case "WINNT":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofDriverVersion("8.52.322.2201");
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ break;
+ case "Darwin":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofOSVersion(0xa0900);
+ break;
+ case "Android":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofDriverVersion("5");
+ break;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "15.0", "8");
+ await promiseStartupManager();
+
+ function checkBlocklist() {
+ var failureId = {};
+ var status;
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_DIRECT2D,
+ failureId
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+ Assert.equal(failureId.value, "FEATURE_FAILURE_DL_BLOCKLIST_g1");
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS,
+ failureId
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+ Assert.equal(failureId.value, "FEATURE_FAILURE_DL_BLOCKLIST_g2");
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_DIRECT3D_10_LAYERS,
+ failureId
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+ Assert.equal(failureId.value, "");
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_DIRECT3D_10_1_LAYERS,
+ failureId
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+ Assert.equal(failureId.value, "");
+
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_OPENGL_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_WEBGL_OPENGL,
+ failureId
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+ Assert.equal(failureId.value, "FEATURE_FAILURE_DL_BLOCKLIST_g11");
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_WEBGL_ANGLE,
+ failureId
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+ Assert.equal(failureId.value, "FEATURE_FAILURE_DL_BLOCKLIST_NO_ID");
+
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBGL2, failureId);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+ Assert.equal(failureId.value, "FEATURE_FAILURE_DL_BLOCKLIST_NO_ID");
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_STAGEFRIGHT,
+ failureId
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION_H264,
+ failureId
+ );
+ if (OS == "Android" && status != Ci.nsIGfxInfo.FEATURE_STATUS_OK) {
+ // Hardware acceleration for H.264 varies by device.
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE);
+ Assert.equal(failureId.value, "FEATURE_FAILURE_WEBRTC_H264");
+ } else {
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+ }
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION_ENCODE,
+ failureId
+ );
+ if (OS == "Android" && status != Ci.nsIGfxInfo.FEATURE_STATUS_OK) {
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE);
+ Assert.equal(failureId.value, "FEATURE_FAILURE_WEBRTC_ENCODE");
+ } else {
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+ }
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION_DECODE,
+ failureId
+ );
+ if (OS == "Android" && status != Ci.nsIGfxInfo.FEATURE_STATUS_OK) {
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE);
+ Assert.equal(failureId.value, "FEATURE_FAILURE_WEBRTC_DECODE");
+ } else {
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+ }
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_DIRECT3D_11_LAYERS,
+ failureId
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_HARDWARE_VIDEO_DECODING,
+ failureId
+ );
+ if (OS == "Linux" && status != Ci.nsIGfxInfo.FEATURE_STATUS_OK) {
+ // Linux test suite is running on SW OpenGL backend and we disable
+ // HW video decoding there.
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_PLATFORM_TEST);
+ Assert.equal(
+ failureId.value,
+ "FEATURE_FAILURE_VIDEO_DECODING_TEST_FAILED"
+ );
+ } else {
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+ }
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_DIRECT3D_11_ANGLE,
+ failureId
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ status = gfxInfo.getFeatureStatus(
+ Ci.nsIGfxInfo.FEATURE_DX_INTEROP2,
+ failureId
+ );
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(checkBlocklist);
+ }, "blocklist-data-gfxItems");
+
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist_AllOS.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_prefs.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_prefs.js
new file mode 100644
index 0000000000..34e92b0e80
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_prefs.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test whether the blacklist successfully adds and removes the prefs that store
+// its decisions when the remote blacklist is changed.
+// Uses test_gfxBlacklist.json and test_gfxBlacklist2.json
+
+// Performs the initial setup
+async function run_test() {
+ try {
+ var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
+ } catch (e) {
+ do_test_finished();
+ return;
+ }
+
+ // We can't do anything if we can't spoof the stuff we need.
+ if (!(gfxInfo instanceof Ci.nsIGfxInfoDebug)) {
+ do_test_finished();
+ return;
+ }
+
+ gfxInfo.QueryInterface(Ci.nsIGfxInfoDebug);
+
+ // Set the vendor/device ID, etc, to match the test file.
+ switch (Services.appinfo.OS) {
+ case "WINNT":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofDriverVersion("8.52.322.2201");
+ // Windows 7
+ gfxInfo.spoofOSVersion(0x60001);
+ break;
+ case "Linux":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ break;
+ case "Darwin":
+ gfxInfo.spoofVendorID("0xabcd");
+ gfxInfo.spoofDeviceID("0x1234");
+ gfxInfo.spoofOSVersion(0xa0900);
+ break;
+ case "Android":
+ gfxInfo.spoofVendorID("abcd");
+ gfxInfo.spoofDeviceID("asdf");
+ gfxInfo.spoofDriverVersion("5");
+ break;
+ }
+
+ do_test_pending();
+
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+ await promiseStartupManager();
+
+ function blacklistAdded(aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(ensureBlacklistSet);
+ }
+ function ensureBlacklistSet() {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+
+ // Make sure unrelated features aren't affected
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ Assert.equal(
+ Services.prefs.getIntPref("gfx.blacklist.direct2d"),
+ Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION
+ );
+
+ Services.obs.removeObserver(blacklistAdded, "blocklist-data-gfxItems");
+ Services.obs.addObserver(blacklistRemoved, "blocklist-data-gfxItems");
+ mockGfxBlocklistItems([
+ {
+ os: "WINNT 6.1",
+ vendor: "0xabcd",
+ devices: ["0x2783", "0x2782"],
+ feature: " DIRECT2D ",
+ featureStatus: " BLOCKED_DRIVER_VERSION ",
+ driverVersion: " 8.52.322.2202 ",
+ driverVersionComparator: " LESS_THAN ",
+ },
+ {
+ os: "WINNT 6.0",
+ vendor: "0xdcba",
+ devices: ["0x2783", "0x1234", "0x2782"],
+ feature: " DIRECT3D_9_LAYERS ",
+ featureStatus: " BLOCKED_DRIVER_VERSION ",
+ driverVersion: " 8.52.322.2202 ",
+ driverVersionComparator: " LESS_THAN ",
+ },
+ ]);
+ }
+
+ function blacklistRemoved(aSubject, aTopic, aData) {
+ // If we wait until after we go through the event loop, gfxInfo is sure to
+ // have processed the gfxItems event.
+ executeSoon(ensureBlacklistUnset);
+ }
+ function ensureBlacklistUnset() {
+ var status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT2D);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ // Make sure unrelated features aren't affected
+ status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_DIRECT3D_9_LAYERS);
+ Assert.equal(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
+
+ var exists = false;
+ try {
+ Services.prefs.getIntPref("gfx.blacklist.direct2d");
+ exists = true;
+ } catch (e) {}
+
+ Assert.ok(!exists);
+
+ do_test_finished();
+ }
+
+ Services.obs.addObserver(blacklistAdded, "blocklist-data-gfxItems");
+ mockGfxBlocklistItemsFromDisk("../data/test_gfxBlacklist.json");
+}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_softblocked.js b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_softblocked.js
new file mode 100644
index 0000000000..edf53183d0
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_softblocked.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// useMLBF=true only supports hard blocks, not soft blocks.
+enable_blocklist_v2_instead_of_useMLBF();
+
+// Tests that an appDisabled add-on that becomes softBlocked remains disabled
+// when becoming appEnabled
+add_task(async function test_softblock() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+ await promiseStartupManager();
+
+ await promiseInstallWebExtension({
+ manifest: {
+ name: "Softblocked add-on",
+ version: "1.0",
+ browser_specific_settings: {
+ gecko: {
+ id: "softblock1@tests.mozilla.org",
+ strict_min_version: "2",
+ strict_max_version: "3",
+ },
+ },
+ },
+ });
+ let s1 = await promiseAddonByID("softblock1@tests.mozilla.org");
+
+ // Make sure to mark it as previously enabled.
+ await s1.enable();
+
+ Assert.ok(!s1.softDisabled);
+ Assert.ok(s1.appDisabled);
+ Assert.ok(!s1.isActive);
+
+ await AddonTestUtils.loadBlocklistRawData({
+ extensions: [
+ {
+ guid: "softblock1@tests.mozilla.org",
+ versionRange: [
+ {
+ severity: "1",
+ },
+ ],
+ },
+ ],
+ });
+
+ Assert.ok(s1.softDisabled);
+ Assert.ok(s1.appDisabled);
+ Assert.ok(!s1.isActive);
+
+ AddonTestUtils.appInfo.platformVersion = "2";
+ await promiseRestartManager("2");
+
+ s1 = await promiseAddonByID("softblock1@tests.mozilla.org");
+
+ Assert.ok(s1.softDisabled);
+ Assert.ok(!s1.appDisabled);
+ Assert.ok(!s1.isActive);
+});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/xpcshell.ini
new file mode 100644
index 0000000000..f13a7645c2
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/xpcshell.ini
@@ -0,0 +1,67 @@
+[DEFAULT]
+tags = addons blocklist
+head = head.js ../head_addons.js
+firefox-appdir = browser
+support-files =
+ ../data/**
+ ../../xpinstall/webmidi_permission.xpi
+
+[test_android_blocklist_dump.js]
+run-if = os == "android"
+[test_blocklist_addonBlockURL.js]
+[test_blocklist_appversion.js]
+skip-if = os == "android" && verify # times out
+[test_blocklist_clients.js]
+tags = remote-settings
+[test_blocklist_gfx.js]
+[test_blocklist_metadata_filters.js]
+[test_blocklist_mlbf.js]
+[test_blocklist_mlbf_dump.js]
+skip-if = os == "android" # blocklist v3 is not bundled with Android builds, see test_android_blocklist_dump.js instead.
+[test_blocklist_mlbf_fetch.js]
+[test_blocklist_mlbf_stashes.js]
+[test_blocklist_mlbf_telemetry.js]
+skip-if =
+ appname == "thunderbird" # Data irrelevant to Thunderbird. Bug 1641400.
+[test_blocklist_mlbf_update.js]
+[test_blocklist_osabi.js]
+skip-if = os == "android" && verify # times out
+[test_blocklist_prefs.js]
+[test_blocklist_regexp_split.js]
+[test_blocklist_severities.js]
+[test_blocklist_statechange_telemetry.js]
+skip-if =
+ appname == "thunderbird" # Data irrelevant to Thunderbird. Bug 1641400.
+[test_blocklist_targetapp_filter.js]
+tags = remote-settings
+[test_blocklist_telemetry.js]
+tags = remote-settings
+skip-if =
+ appname == "thunderbird" # Data irrelevant to Thunderbird. Bug 1641400.
+[test_blocklistchange.js]
+# Times out during parallel runs on desktop
+requesttimeoutfactor = 2
+skip-if = os == "android" && verify # times out because it takes too much time to run the full test
+[test_blocklistchange_v2.js]
+# Times out during parallel runs on desktop
+requesttimeoutfactor = 2
+skip-if = os == "android" && verify # times out in chaos mode on Android because several minutes are spent waiting at https://hg.mozilla.org/mozilla-central/file/3350b680/toolkit/mozapps/extensions/Blocklist.jsm#l698
+[test_gfxBlacklist_Device.js]
+[test_gfxBlacklist_DriverNew.js]
+[test_gfxBlacklist_Equal_DriverNew.js]
+[test_gfxBlacklist_Equal_DriverOld.js]
+[test_gfxBlacklist_Equal_OK.js]
+[test_gfxBlacklist_GTE_DriverOld.js]
+[test_gfxBlacklist_GTE_OK.js]
+[test_gfxBlacklist_No_Comparison.js]
+[test_gfxBlacklist_OK.js]
+[test_gfxBlacklist_OS.js]
+[test_gfxBlacklist_OSVersion_match.js]
+[test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js]
+[test_gfxBlacklist_OSVersion_mismatch_OSVersion.js]
+[test_gfxBlacklist_Vendor.js]
+[test_gfxBlacklist_Version.js]
+[test_gfxBlacklist_prefs.js]
+# Bug 1248787 - consistently fails
+skip-if = true
+[test_softblocked.js]