Source code
Revision control
Copy as Markdown
Other Tools
/* Any copyright is dedicated to the Public Domain.
"use strict";
const { sinon } = ChromeUtils.importESModule(
);
Services.prefs.setBoolPref("extensions.blocklist.useMLBF", true);
Services.prefs.setBoolPref("extensions.blocklist.softblock.enabled", true);
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
const { STATE_NOT_BLOCKED, STATE_SOFTBLOCKED, STATE_BLOCKED } =
Ci.nsIBlocklistService;
function getTestExtensionDefinition(
addonId,
addonVersion,
addonType = "extension",
{ hidden = false } = {}
) {
let manifestData = {};
let files = {};
switch (addonType) {
case "extension":
manifestData = { hidden };
break;
case "theme":
manifestData = { theme: {} };
break;
case "dictionary":
manifestData = { dictionaries: { "en-US": "en-US.dic" } };
files = { "en-US.dic": "", "en-US.aff": "" };
break;
case "locale":
manifestData = {
langpack_id: "und",
languages: {},
};
break;
default:
throw new Error(
`Unexpected addonType for ${addonId}:${addonVersion}: ${addonType}`
);
}
return {
useAddonManager: "permanent",
manifest: {
...manifestData,
version: addonVersion,
browser_specific_settings: { gecko: { id: addonId } },
},
files,
};
}
async function installTestExtension(addonId) {
const extension = ExtensionTestUtils.loadExtension(
getTestExtensionDefinition(addonId, "1.0", "extension")
);
await extension.startup();
const addon = await AddonManager.getAddonByID(addonId);
Assert.equal(
addon.type,
"extension",
`${addonId} should have the expected addon type`
);
return extension;
}
async function installTestAddon(addonId, addonType, { hidden = false } = {}) {
const xpi = AddonTestUtils.createTempWebExtensionFile(
getTestExtensionDefinition(addonId, "1.0", addonType, { hidden })
);
await AddonTestUtils.promiseInstallFile(xpi);
const addon = await AddonManager.getAddonByID(addonId);
Assert.equal(
addon.type,
addonType,
`${addonId} should have the expected addon type`
);
Assert.equal(
addon.hidden,
hidden,
`${addonId} is expected to ${hidden ? "" : "NOT"} be hidden`
);
return addon;
}
function assertXPIDatabaseBlocklistAttentionAddonIds(expectedAddonIdsArray) {
const { XPIDatabase } = AddonTestUtils.getXPIExports();
Assert.deepEqual(
expectedAddonIdsArray,
Array.from(XPIDatabase.blocklistAttentionAddonIdsSet),
"Got the expected addon ids in the XPIDatabase.blocklistAttentionAddonIdsSet"
);
}
function getBlockKey({ id, version }) {
if (!id || !version) {
// Throw an error if the resulting block key would not be a valid one.
throw new Error(
"getBlockKey requires id and version to be defined and non-empty"
);
}
return `${id}:${version}`;
}
function createStashRecord({
blocked = [],
softblocked = [],
unblocked = [],
stash_time = 0,
} = {}) {
return {
stash_time,
stash: {
blocked: blocked.map(getBlockKey),
softblocked: softblocked.map(getBlockKey),
unblocked: unblocked.map(getBlockKey),
},
};
}
async function assertBlocklistAttentionInfo(expected) {
let baInfo = await AddonManager.getBlocklistAttentionInfo();
Assert.deepEqual(
baInfo.addons?.map(it => it.id),
expected.addons,
"blocklistAttentionInfo.addons"
);
Assert.equal(
baInfo.shouldShow,
expected.shouldShow,
"blocklistAttentionInfo.shouldShow"
);
Assert.equal(
baInfo.hasSoftBlocked,
expected.hasSoftBlocked,
"blocklistAttentionInfo.hasSoftBlocked"
);
Assert.equal(
baInfo.hasHardBlocked,
expected.hasHardBlocked,
"blocklistAttentionInfo.hasHardBlocked"
);
Assert.equal(
baInfo.extensionsCount,
expected.addons.length,
"blocklistAttentionInfo.extensionsCount"
);
return baInfo;
}
async function testBlocklistAttentionScenario({
blocklistStashRecords,
expectBeforeDismiss,
expectAfterDismiss,
}) {
if (blocklistStashRecords) {
info("Loading test blocklist data");
await AddonTestUtils.loadBlocklistRawData({
extensionsMLBF: blocklistStashRecords,
});
}
info("Verify expectations before dismissing the blocklist attention info");
for (const [addonId, blocklistState] of Object.entries(
expectBeforeDismiss.blocklistStates
)) {
// Sanity check the expected addon blocklist states.
const addon = await AddonManager.getAddonByID(addonId);
Assert.equal(
addon?.blocklistState,
blocklistState,
`Got expected blocklistState for ${addonId}`
);
}
for (const [addonId, blocklistAttentionDismissed] of Object.entries(
expectBeforeDismiss.dismissedStates
)) {
const addon = await AddonManager.getAddonByID(addonId);
Assert.equal(
addon?.blocklistAttentionDismissed,
blocklistAttentionDismissed,
`Got expected blocklistAttentionDismissed for ${addonId}`
);
}
Assert.equal(
AddonManager.shouldShowBlocklistAttention(),
expectBeforeDismiss.shouldShowBlocklistAttention,
"AddonManager.shouldShowBlocklistAttention()"
);
const baInfo = await assertBlocklistAttentionInfo(
expectBeforeDismiss.blocklistAttentionInfo
);
if (!expectAfterDismiss) {
return;
}
let managerListener = {
onBlocklistAttentionUpdated: sinon.spy(),
};
AddonManager.addManagerListener(managerListener);
baInfo.dismiss();
AddonManager.removeManagerListener(managerListener);
info("Verify expected managerListener call count");
Assert.equal(
managerListener.onBlocklistAttentionUpdated.callCount,
expectAfterDismiss.managerListenerCallCount ?? 0,
"Got the expected number of call to onBlocklistAttentionUpdated manager listeners"
);
info("Verify expectations before dismissing the blocklist attention info");
for (const [addonId, blocklistAttentionDismissed] of Object.entries(
expectAfterDismiss.dismissedStates
)) {
const addon = await AddonManager.getAddonByID(addonId);
Assert.equal(
addon?.blocklistAttentionDismissed,
blocklistAttentionDismissed,
`Got expected blocklistAttentionDismissed for ${addonId}`
);
}
Assert.equal(
AddonManager.shouldShowBlocklistAttention(),
expectAfterDismiss.shouldShowBlocklistAttention,
"AddonManager.shouldShowBlocklistAttention()"
);
{
const expected = expectAfterDismiss.blocklistAttentionInfo;
Assert.equal(
baInfo.shouldShow,
expected.shouldShow,
"blocklistAttentionInfo.shouldShow"
);
}
}
add_setup(async function setup() {
await promiseStartupManager();
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [] });
});
add_task(async function test_blocklist_attention_basic() {
const ext1 = await installTestExtension("@ext1");
const ext2 = await installTestExtension("@ext2");
const ext3 = await installTestExtension("@ext3");
const addon1 = await AddonManager.getAddonByID(ext1.id);
const addon2 = await AddonManager.getAddonByID(ext2.id);
const addon3 = await AddonManager.getAddonByID(ext3.id);
// Sanity checks.
for (const addon of [addon1, addon2, addon3]) {
Assert.ok(addon, "Expect addon to be found");
Assert.equal(
addon.blocklistState,
STATE_NOT_BLOCKED,
"Expect addon to not be blocked"
);
Assert.ok(addon.isActive, "Expect addon to be enabled");
Assert.ok(
!addon.blocklistAttentionDismissed,
"Expect addon.blocklistAttentionDismissed to be false"
);
}
Assert.ok(
!AddonManager.shouldShowBlocklistAttention(),
"Expect shouldShowBlocklistAttention to be initially returning false"
);
info("Test blocklist attention info on a single hard-blocked addon");
await testBlocklistAttentionScenario({
blocklistStashRecords: [createStashRecord({ blocked: [addon1] })],
expectBeforeDismiss: {
blocklistStates: {
[addon1.id]: STATE_BLOCKED,
[addon2.id]: STATE_NOT_BLOCKED,
[addon3.id]: STATE_NOT_BLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: false,
hasHardBlocked: true,
addons: [addon1.id],
},
dismissedStates: {
[addon1.id]: false,
[addon2.id]: false,
[addon3.id]: false,
},
shouldShowBlocklistAttention: true,
},
expectAfterDismiss: {
managerListenerCallCount: 1,
blocklistAttentionInfo: {
shouldShow: false,
},
dismissedStates: {
[addon1.id]: true,
[addon2.id]: false,
[addon3.id]: false,
},
shouldShowBlocklistAttention: false,
},
});
info("Test blocklist attention info on new single soft-blocked addon");
await testBlocklistAttentionScenario({
blocklistStashRecords: [
createStashRecord({ blocked: [addon1] }),
createStashRecord({ softblocked: [addon2] }),
],
expectBeforeDismiss: {
blocklistStates: {
[addon1.id]: STATE_BLOCKED,
[addon2.id]: STATE_SOFTBLOCKED,
[addon3.id]: STATE_NOT_BLOCKED,
},
// blocklistAttentionInfo should reflect that the attention
// on addon1 hard-block was already dismissed before.
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: true,
hasHardBlocked: false,
addons: [addon2.id],
},
dismissedStates: {
[addon1.id]: true, // Dismissed as part of the previous scenario.
[addon2.id]: false,
[addon3.id]: false,
},
shouldShowBlocklistAttention: true,
},
expectAfterDismiss: {
managerListenerCallCount: 1,
blocklistAttentionInfo: {
shouldShow: false,
},
dismissedStates: {
[addon1.id]: true,
[addon2.id]: true,
[addon3.id]: false,
},
shouldShowBlocklistAttention: false,
},
});
info("Test blocklist attention info after all blocks are removed");
await testBlocklistAttentionScenario({
blocklistStashRecords: [createStashRecord()],
expectBeforeDismiss: {
blocklistStates: {
[addon1.id]: STATE_NOT_BLOCKED,
[addon2.id]: STATE_NOT_BLOCKED,
[addon3.id]: STATE_NOT_BLOCKED,
},
blocklistAttentionInfo: {
shouldShow: false,
hasSoftBlocked: false,
hasHardBlocked: false,
addons: [],
},
// The flag should have been cleared when the blocklistState for the test addons changed
// to STATE_NOT_BLOCKED as a side-effect of the blocklist stash record used by this call.
dismissedStates: {
[addon1.id]: false,
[addon2.id]: false,
[addon3.id]: false,
},
shouldShowBlocklistAttention: false,
},
expectAfterDismiss: {
managerListenerCallCount: 0,
blocklistAttentionInfo: {
shouldShow: false,
},
// None of the 3 test addons should have the blocklistAttentionDismissed flag set
// to true after blocklistAttentionInfo.dismiss method was called.
dismissedStates: {
[addon1.id]: false,
[addon2.id]: false,
[addon3.id]: false,
},
shouldShowBlocklistAttention: false,
},
});
info(
"Test blocklist attention info after new hard and soft blocks have been received"
);
await testBlocklistAttentionScenario({
blocklistStashRecords: [
createStashRecord({ blocked: [addon2], softblocked: [addon3] }),
],
expectBeforeDismiss: {
blocklistStates: {
[addon1.id]: STATE_NOT_BLOCKED,
[addon2.id]: STATE_BLOCKED,
[addon3.id]: STATE_SOFTBLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: true,
hasHardBlocked: true,
addons: [addon2.id, addon3.id],
},
dismissedStates: {
[addon1.id]: false,
[addon2.id]: false,
[addon3.id]: false,
},
shouldShowBlocklistAttention: true,
},
expectAfterDismiss: {
managerListenerCallCount: 2,
blocklistAttentionInfo: {
shouldShow: false,
},
dismissedStates: {
[addon1.id]: false,
[addon2.id]: true,
[addon3.id]: true,
},
shouldShowBlocklistAttention: false,
},
});
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [] });
await ext3.unload();
await ext2.unload();
await ext1.unload();
});
add_task(async function test_attentions_reinitalized_on_restart() {
const ext1 = await installTestExtension("@ext1");
const ext2 = await installTestExtension("@ext2");
const ext3 = await installTestExtension("@ext3");
// Test blocklist attention without dismissing it.
await testBlocklistAttentionScenario({
blocklistStashRecords: [
createStashRecord({ blocked: [ext1], softblocked: [ext2] }),
],
expectBeforeDismiss: {
blocklistStates: {
[ext1.id]: STATE_BLOCKED,
[ext2.id]: STATE_SOFTBLOCKED,
[ext3.id]: STATE_NOT_BLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: true,
hasHardBlocked: true,
addons: [ext1.id, ext2.id],
},
dismissedStates: {
[ext1.id]: false,
[ext2.id]: false,
[ext3.id]: false,
},
shouldShowBlocklistAttention: true,
},
});
assertXPIDatabaseBlocklistAttentionAddonIds([ext1.id, ext2.id]);
info("Simulate AOM shutdown");
await promiseShutdownManager();
AddonTestUtils.getXPIExports().XPIDatabase.clearBlocklistAttentionAddonIdsSet();
assertXPIDatabaseBlocklistAttentionAddonIds([]);
const promiseBlocklistAttentionUpdated = AddonTestUtils.promiseManagerEvent(
"onBlocklistAttentionUpdated",
() => {
const { XPIDatabase } = AddonTestUtils.getXPIExports();
// The BlocklistAttentionUpdated may have been fired more than once,
// but we want to be sure that it is fired at least once when the
// XPIDatabase blocklistAttention add-on ids has been populated
// with all the expected addon ids.
return XPIDatabase.blocklistAttentionAddonIdsSet.size == 2;
}
);
info("Simulate new AOM startup");
await promiseStartupManager();
info("Wait for onBlocklistAttentionUpdated Manager listener to be called");
await promiseBlocklistAttentionUpdated;
assertXPIDatabaseBlocklistAttentionAddonIds([ext1.id, ext2.id]);
// Test blocklist attention again after AOM restart
await testBlocklistAttentionScenario({
expectBeforeDismiss: {
blocklistStates: {
[ext1.id]: STATE_BLOCKED,
[ext2.id]: STATE_SOFTBLOCKED,
[ext3.id]: STATE_NOT_BLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: true,
hasHardBlocked: true,
addons: [ext1.id, ext2.id],
},
dismissedStates: {
[ext1.id]: false,
[ext2.id]: false,
[ext3.id]: false,
},
shouldShowBlocklistAttention: true,
},
});
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [] });
await ext3.unload();
await ext2.unload();
await ext1.unload();
});
add_task(async function test_dismissed_flag_cleared_on_addon_updates() {
const ext1 = await installTestExtension("@ext1");
const ext2 = await installTestExtension("@ext2");
await testBlocklistAttentionScenario({
blocklistStashRecords: [createStashRecord({ blocked: [ext1, ext2] })],
expectBeforeDismiss: {
blocklistStates: {
[ext1.id]: STATE_BLOCKED,
[ext2.id]: STATE_BLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: false,
hasHardBlocked: true,
addons: [ext1.id, ext2.id],
},
dismissedStates: {
[ext1.id]: false,
[ext2.id]: false,
},
shouldShowBlocklistAttention: true,
},
expectAfterDismiss: {
managerListenerCallCount: 2,
blocklistAttentionInfo: {
shouldShow: false,
},
dismissedStates: {
[ext1.id]: true,
[ext2.id]: true,
},
shouldShowBlocklistAttention: false,
},
});
await ext1.upgrade(getTestExtensionDefinition(ext1.id, "2.0"));
Assert.equal(
ext1.version,
"2.0",
"Expect test extension version changed to 2.0"
);
const addon1v2 = await AddonManager.getAddonByID(ext1.id);
Assert.equal(
addon1v2.version,
"2.0",
"Expect new AddonWrapper instance version to be 2.0"
);
// The blocklistAttentionDismissed field is not propagated from the old to the new AddonInternal
// instances when the addon is being updated, that ensures the flag is implicitly cleared
// when the addon is updated.
Assert.equal(
addon1v2.blocklistAttentionDismissed,
false,
"Expect new AddonWrapper instance blocklistAttentionDismissed to be false"
);
// The XPIDatabase blocklistAttentionAddonIdsSet is expected to be empty as a side-effect
// of the user dismissing the blocklist attention.
assertXPIDatabaseBlocklistAttentionAddonIds([]);
await promiseRestartManager();
// After restarting the AddonManager we expect the XPIDatabase blocklistAttentionAddonIdsSet
// to still be empty because the only blocked addons have already been dismissed by the user.
assertXPIDatabaseBlocklistAttentionAddonIds([]);
const addon1v2AfterRestart = await AddonManager.getAddonByID(ext1.id);
Assert.equal(
addon1v2AfterRestart.blocklistAttentionDismissed,
false,
"Expect addon1 blocklistAttentionDismissed to be false"
);
// addon2 was not updated and so we expect the blocklistAttentionDismissed flag to have been
// loaded from the addonDB and still be set to true.
const addon2 = await AddonManager.getAddonByID(ext2.id);
Assert.equal(
addon2.blocklistAttentionDismissed,
true,
"Expect addon2 blocklistAttentionDismissed to be true"
);
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [] });
await ext2.unload();
await ext1.unload();
});
add_task(async function test_blocklist_attention_on_reenabled_softblock() {
async function assertExtensionBlocklistStatePropagated(addonId, msgSuffix) {
const addon = await AddonManager.getAddonByID(addonId);
Assert.equal(
addon.blocklistState,
WebExtensionPolicy.getByID(addonId).extension.blocklistState,
`Expect extension.blocklistState to match addon.blocklistState ${msgSuffix}`
);
Assert.equal(
WebExtensionPolicy.getByID(addonId).extension.isSoftBlocked,
addon.blocklistState === STATE_SOFTBLOCKED,
`Expect extension.isSoftBlocked to be false ${msgSuffix}`
);
}
const ADDON_ID = "@ext1";
const ext1 = await installTestExtension(ADDON_ID);
await assertExtensionBlocklistStatePropagated(
ADDON_ID,
"(not blocked extension)"
);
await promiseRestartManager();
await ext1.awaitStartup();
await assertExtensionBlocklistStatePropagated(
ADDON_ID,
"(not blocked extension - after startup)"
);
await testBlocklistAttentionScenario({
blocklistStashRecords: [createStashRecord({ softblocked: [ext1] })],
expectBeforeDismiss: {
blocklistStates: {
[ext1.id]: STATE_SOFTBLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: true,
hasHardBlocked: false,
addons: [ext1.id],
},
dismissedStates: {
[ext1.id]: false,
},
shouldShowBlocklistAttention: true,
},
});
let addon1 = await AddonManager.getAddonByID(ext1.id);
Assert.equal(
addon1.softDisabled,
true,
"Expect test addon to be softDisabled"
);
const promiseBlocklistAttentionUpdated = AddonTestUtils.promiseManagerEvent(
"onBlocklistAttentionUpdated"
);
await addon1.enable();
Assert.equal(
addon1.softDisabled,
false,
"Expect test addon to NOT be softDisabled after being explicitly enabled"
);
info(
"Wait for onBlocklistAttentionUpdated Manager listeners to have been called"
);
await promiseBlocklistAttentionUpdated;
await assertExtensionBlocklistStatePropagated(
ADDON_ID,
"(softblocked extension re-enabled)"
);
info("Verify again after AOM restart");
await promiseRestartManager();
await ext1.awaitStartup();
await assertExtensionBlocklistStatePropagated(
ADDON_ID,
"(softblocked extension re-enabled - after startup)"
);
info(
"Verify blocklistAttentionInfo again after softblocked addon has been re-enabled"
);
await testBlocklistAttentionScenario({
expectBeforeDismiss: {
blocklistStates: {
[ext1.id]: STATE_SOFTBLOCKED,
},
blocklistAttentionInfo: {
shouldShow: false,
hasSoftBlocked: false,
hasHardBlocked: false,
addons: [],
},
dismissedStates: {
[ext1.id]: false,
},
shouldShowBlocklistAttention: false,
},
});
info(
"Verify onPropertyChanged call and extension.blocklistState update on softblock removal"
);
addon1 = await AddonManager.getAddonByID(ext1.id);
const promisePropertyChanged = AddonTestUtils.promiseAddonEvent(
"onPropertyChanged",
(addon, properties) =>
addon.id === addon1.id && properties.includes("blocklistState")
);
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [] });
Assert.equal(
addon1.blocklistState,
STATE_NOT_BLOCKED,
"Expect addon.blocklistState to be STATE_NOT_BLOCKED (softblock lifted)"
);
info("Wait for onPropertyChanged Addon listeners to have been called");
await promisePropertyChanged;
await assertExtensionBlocklistStatePropagated(ADDON_ID, "(softblock lifted)");
info("Verify again after AOM restart");
await promiseRestartManager();
await ext1.awaitStartup();
addon1 = await AddonManager.getAddonByID(ext1.id);
Assert.equal(
addon1.blocklistState,
STATE_NOT_BLOCKED,
"Expect addon.blocklistState to be STATE_NOT_BLOCKED (softblock lifted)"
);
await assertExtensionBlocklistStatePropagated(
ADDON_ID,
"(softblocked lifted - after startup)"
);
await ext1.unload();
});
add_task(async function test_attention_after_uninstalls() {
const ext1 = await installTestExtension("@ext1");
const ext2 = await installTestExtension("@ext2");
let managerListener = {
onBlocklistAttentionUpdated: sinon.spy(),
};
AddonManager.addManagerListener(managerListener);
await testBlocklistAttentionScenario({
blocklistStashRecords: [
createStashRecord({ blocked: [ext1], softblocked: [ext2] }),
],
expectBeforeDismiss: {
blocklistStates: {
[ext1.id]: STATE_BLOCKED,
[ext2.id]: STATE_SOFTBLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: true,
hasHardBlocked: true,
addons: [ext1.id, ext2.id],
},
dismissedStates: {
[ext1.id]: false,
[ext2.id]: false,
},
shouldShowBlocklistAttention: true,
},
});
Assert.equal(
managerListener.onBlocklistAttentionUpdated.callCount,
2,
"Expect onBlocklistAttentionUpdated to have been called once for each of the blocked extensions"
);
const addon1 = await AddonManager.getAddonByID(ext1.id);
managerListener.onBlocklistAttentionUpdated.resetHistory();
await addon1.uninstall();
Assert.equal(
managerListener.onBlocklistAttentionUpdated.callCount,
1,
"Expect onBlocklistAttentionUpdated to have been called once after ext1 uninstalled"
);
await testBlocklistAttentionScenario({
expectBeforeDismiss: {
blocklistStates: {
[ext2.id]: STATE_SOFTBLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: true,
hasHardBlocked: false,
addons: [ext2.id],
},
dismissedStates: {
[ext2.id]: false,
},
shouldShowBlocklistAttention: true,
},
});
const addon2 = await AddonManager.getAddonByID(ext2.id);
managerListener.onBlocklistAttentionUpdated.resetHistory();
await addon2.uninstall();
Assert.equal(
managerListener.onBlocklistAttentionUpdated.callCount,
1,
"Expect onBlocklistAttentionUpdated to have been called again after ext2 uninstalled"
);
await testBlocklistAttentionScenario({
expectBeforeDismiss: {
blocklistStates: {},
blocklistAttentionInfo: {
shouldShow: false,
hasSoftBlocked: false,
hasHardBlocked: false,
addons: [],
},
dismissedStates: {},
shouldShowBlocklistAttention: false,
},
});
AddonManager.removeManagerListener(managerListener);
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [] });
});
add_task(async function test_hidden_addons() {
const extensionAddon = await installTestAddon("@extension", "extension");
const hiddenAddon1 = await installTestAddon(
"@hidden-extension1",
"extension",
{
hidden: true,
}
);
const hiddenAddon2 = await installTestAddon(
"@hidden-extension2",
"extension",
{
hidden: true,
}
);
await testBlocklistAttentionScenario({
blocklistStashRecords: [
createStashRecord({
blocked: [extensionAddon, hiddenAddon1],
softblocked: [hiddenAddon2],
}),
],
expectBeforeDismiss: {
blocklistStates: {
[extensionAddon.id]: STATE_BLOCKED,
[hiddenAddon1.id]: STATE_BLOCKED,
[hiddenAddon2.id]: STATE_SOFTBLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: false,
hasHardBlocked: true,
addons: [extensionAddon.id],
},
dismissedStates: {
[extensionAddon.id]: false,
[hiddenAddon1.id]: false,
[hiddenAddon2.id]: false,
},
shouldShowBlocklistAttention: true,
},
expectAfterDismiss: {
managerListenerCallCount: 1,
blocklistAttentionInfo: {
shouldShow: false,
},
dismissedStates: {
[extensionAddon.id]: true,
[hiddenAddon1.id]: false,
[hiddenAddon2.id]: false,
},
shouldShowBlocklistAttention: false,
},
});
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [] });
await hiddenAddon2.uninstall();
await hiddenAddon1.uninstall();
await extensionAddon.uninstall();
});
// Android builds do not support the other XPIProvider addon types.
add_task(
{ skip_if: () => IS_ANDROID_BUILD },
async function test_other_addon_types() {
const extensionAddon = await installTestAddon("@extension", "extension");
const themeAddon = await installTestAddon("@theme", "theme");
const localeAddon = await installTestAddon("@langpack", "locale");
const dictionaryAddon = await installTestAddon("@dictionary", "dictionary");
await testBlocklistAttentionScenario({
blocklistStashRecords: [
createStashRecord({
blocked: [extensionAddon, localeAddon, dictionaryAddon],
softblocked: [themeAddon],
}),
],
expectBeforeDismiss: {
blocklistStates: {
[extensionAddon.id]: STATE_BLOCKED,
[themeAddon.id]: STATE_SOFTBLOCKED,
[localeAddon.id]: STATE_BLOCKED,
[dictionaryAddon.id]: STATE_BLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: false,
hasHardBlocked: true,
addons: [extensionAddon.id],
},
dismissedStates: {
[extensionAddon.id]: false,
[themeAddon.id]: false,
[localeAddon.id]: false,
[dictionaryAddon.id]: false,
},
shouldShowBlocklistAttention: true,
},
expectAfterDismiss: {
managerListenerCallCount: 1,
blocklistAttentionInfo: {
shouldShow: false,
},
dismissedStates: {
[extensionAddon.id]: true,
[themeAddon.id]: false,
[localeAddon.id]: false,
[dictionaryAddon.id]: false,
},
shouldShowBlocklistAttention: false,
},
});
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [] });
await dictionaryAddon.uninstall();
await localeAddon.uninstall();
await themeAddon.uninstall();
await extensionAddon.uninstall();
}
);
add_task(async function test_blocklist_attention_on_blocklist_updates() {
let managerListener = {
onBlocklistAttentionUpdated: sinon.spy(),
};
function assertManagerListenerCallCount(expectedCallCount, msg) {
Assert.equal(
managerListener.onBlocklistAttentionUpdated.callCount,
expectedCallCount,
msg
);
managerListener.onBlocklistAttentionUpdated.resetHistory();
}
AddonManager.addManagerListener(managerListener);
const extensionAddon = await installTestAddon("@extension", "extension");
assertManagerListenerCallCount(
0,
"Expect no onBlocklistAttentionUpdated calls on a non-blocked install"
);
info(
"Simulate blocklistState change from STATE_NOT_BLOCKED to STATE_BLOCKED"
);
let stashRecords = [
createStashRecord({ blocked: [extensionAddon], stash_time: 0 }),
];
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: stashRecords });
assertManagerListenerCallCount(
1,
"Expect a onBlocklistAttentionUpdated calls on a new hard-block"
);
await testBlocklistAttentionScenario({
expectBeforeDismiss: {
blocklistStates: {
[extensionAddon.id]: STATE_BLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: false,
hasHardBlocked: true,
addons: [extensionAddon.id],
},
dismissedStates: {
[extensionAddon.id]: false,
},
shouldShowBlocklistAttention: true,
},
});
info(
"Simulate blocklistState change from STATE_BLOCKED to STATE_SOFTBLOCKED"
);
stashRecords.push(
createStashRecord({ softblocked: [extensionAddon], stash_time: 1 })
);
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: stashRecords });
// The disabled state of the addon didn't change and so we don't call the manager listener
// because it doesn't change the results we would get by AddonManager.shouldShowBlocklistAttention()
// (e.g. the attention dot wouldn't need to be refreshed).
assertManagerListenerCallCount(
0,
"Expect no onBlocklistAttentionUpdated calls on hard-block to soft-block"
);
// On the contrary we still expect AddonManager.getBlocklistAttentionInfo to reflect the blocklistState
// change.
await testBlocklistAttentionScenario({
expectBeforeDismiss: {
blocklistStates: {
[extensionAddon.id]: STATE_SOFTBLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: true,
hasHardBlocked: false,
addons: [extensionAddon.id],
},
dismissedStates: {
[extensionAddon.id]: false,
},
shouldShowBlocklistAttention: true,
},
});
info(
"Simulate blocklistState change from STATE_SOFTBLOCKED to STATE_BLOCKED"
);
stashRecords.push(
createStashRecord({ blocked: [extensionAddon], stash_time: 2 })
);
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: stashRecords });
// Same as hard to soft block transistion, we expect soft to hard block to not be
// triggering the onBlocklistAttentionUpdated if the addon was disabled also while
// soft blocked.
assertManagerListenerCallCount(
0,
"Expect no onBlocklistAttentionUpdated calls on soft-block to hard-block"
);
await testBlocklistAttentionScenario({
expectBeforeDismiss: {
blocklistStates: {
[extensionAddon.id]: STATE_BLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: false,
hasHardBlocked: true,
addons: [extensionAddon.id],
},
dismissedStates: {
[extensionAddon.id]: false,
},
shouldShowBlocklistAttention: true,
},
});
info(
"Simulate blocklistState change from STATE_BLOCKED to STATE_NOT_BLOCKED"
);
stashRecords.push(
createStashRecord({ unblocked: [extensionAddon], stash_time: 3 })
);
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: stashRecords });
assertManagerListenerCallCount(
1,
"Expect a onBlocklistAttentionUpdated calls on hard-block to not-blocked"
);
await testBlocklistAttentionScenario({
expectBeforeDismiss: {
blocklistStates: {
[extensionAddon.id]: STATE_NOT_BLOCKED,
},
blocklistAttentionInfo: {
shouldShow: false,
hasSoftBlocked: false,
hasHardBlocked: false,
addons: [],
},
dismissedStates: {
[extensionAddon.id]: false,
},
shouldShowBlocklistAttention: false,
},
});
info(
"Simulate blocklistState change from STATE_NOT_BLOCKED to STATE_SOFTBLOCKED"
);
stashRecords.push(
createStashRecord({ softblocked: [extensionAddon], stash_time: 4 })
);
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: stashRecords });
assertManagerListenerCallCount(
1,
"Expect a onBlocklistAttentionUpdated calls on a new soft-block"
);
await testBlocklistAttentionScenario({
expectBeforeDismiss: {
blocklistStates: {
[extensionAddon.id]: STATE_SOFTBLOCKED,
},
blocklistAttentionInfo: {
shouldShow: true,
hasSoftBlocked: true,
hasHardBlocked: false,
addons: [extensionAddon.id],
},
dismissedStates: {
[extensionAddon.id]: false,
},
shouldShowBlocklistAttention: true,
},
});
info(
"Simulate blocklistState change from STATE_SOFTBLOCKED to STATE_NOT_BLOCKED"
);
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [] });
assertManagerListenerCallCount(
1,
"Expect a onBlocklistAttentionUpdated calls on soft-block to not-blocked"
);
await testBlocklistAttentionScenario({
expectBeforeDismiss: {
blocklistStates: {
[extensionAddon.id]: STATE_NOT_BLOCKED,
},
blocklistAttentionInfo: {
shouldShow: false,
hasSoftBlocked: false,
hasHardBlocked: false,
addons: [],
},
dismissedStates: {
[extensionAddon.id]: false,
},
shouldShowBlocklistAttention: false,
},
});
AddonManager.removeManagerListener(managerListener);
await extensionAddon.uninstall();
});
add_task(async function test_blocklist_attention_on_blocked_install() {
const SIGNED_ADDON_XPI_FILE = do_get_file("amosigned.xpi");
const SIGNED_ADDON_ID = "amosigned-xpi@tests.mozilla.org";
const SIGNED_ADDON_VERSION = "2.2";
const SIGNED_ADDON_KEY = `${SIGNED_ADDON_ID}:${SIGNED_ADDON_VERSION}`;
await AddonTestUtils.loadBlocklistRawData({
extensionsMLBF: [
{
stash: {
blocked: [],
softblocked: [SIGNED_ADDON_KEY],
unblocked: [],
},
stash_time: Date.now(),
},
],
});
let managerListener = {
onBlocklistAttentionUpdated: sinon.spy(),
};
AddonManager.addManagerListener(managerListener);
const install = await promiseInstallFile(SIGNED_ADDON_XPI_FILE);
Assert.equal(
install.error,
AddonManager.ERROR_SOFT_BLOCKED,
"Install should have an error"
);
Assert.equal(
managerListener.onBlocklistAttentionUpdated.callCount,
0,
"Expect no onBlocklistAttentionUpdated calls on blocked installs"
);
assertXPIDatabaseBlocklistAttentionAddonIds([]);
await assertBlocklistAttentionInfo({
addons: [],
shouldShow: false,
hasSoftBlocked: false,
hasHardBlocked: false,
});
Assert.ok(
!AddonManager.shouldShowBlocklistAttention(),
"Expect shouldShowBlocklistAttention to be false"
);
AddonManager.removeManagerListener(managerListener);
await AddonTestUtils.loadBlocklistRawData({ extensionsMLBF: [] });
});