diff options
Diffstat (limited to '')
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] |