Bug 1917848 - Show blocklist attention dot and blocklist messagebar message on new hard/soft blocked extensions (AOM/XPIProvider backend changes). r=willdurand

Differential Revision: https://phabricator.services.mozilla.com/D228676
This commit is contained in:
Luca Greco 2024-11-20 17:03:15 +00:00
parent 93ced1b5d9
commit 767e1171fd
6 changed files with 1356 additions and 2 deletions

View File

@ -25,6 +25,8 @@ const MOZ_COMPATIBILITY_NIGHTLY = ![
].includes(AppConstants.MOZ_UPDATE_CHANNEL);
const INTL_LOCALES_CHANGED = "intl:app-locales-changed";
const XPIPROVIDER_BLOCKLIST_ATTENTION_UPDATED =
"xpi-provider:blocklist-attention-updated";
const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled";
@ -694,6 +696,9 @@ var AddonManagerInternal = {
Services.obs.addObserver(this, AMBrowserExtensionsImport.TOPIC_COMPLETE);
Services.obs.addObserver(this, AMBrowserExtensionsImport.TOPIC_PENDING);
// Watch for blocklist attention updates.
Services.obs.addObserver(this, XPIPROVIDER_BLOCKLIST_ATTENTION_UPDATED);
// Ensure all default providers have had a chance to register themselves.
const { XPIExports } = ChromeUtils.importESModule(
"resource://gre/modules/addons/XPIExports.sys.mjs"
@ -995,6 +1000,7 @@ var AddonManagerInternal = {
);
Services.obs.removeObserver(this, AMBrowserExtensionsImport.TOPIC_COMPLETE);
Services.obs.removeObserver(this, AMBrowserExtensionsImport.TOPIC_PENDING);
Services.obs.removeObserver(this, XPIPROVIDER_BLOCKLIST_ATTENTION_UPDATED);
AMRemoteSettings.shutdown();
@ -1065,6 +1071,10 @@ var AddonManagerInternal = {
case AMBrowserExtensionsImport.TOPIC_PENDING:
this.callManagerListeners("onBrowserExtensionsImportChanged");
return;
case XPIPROVIDER_BLOCKLIST_ATTENTION_UPDATED:
this.callManagerListeners("onBlocklistAttentionUpdated");
return;
}
switch (aData) {
@ -3049,6 +3059,30 @@ var AddonManagerInternal = {
return this.getAddonsByTypes(null);
},
shouldShowBlocklistAttention() {
if (!gStarted) {
throw Components.Exception(
"AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED
);
}
return this._getProviderByName(
"XPIProvider"
).shouldShowBlocklistAttention();
},
getBlocklistAttentionInfo() {
if (!gStarted) {
throw Components.Exception(
"AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED
);
}
return this._getProviderByName("XPIProvider").getBlocklistAttentionInfo();
},
/**
* Adds a new AddonManagerListener if the listener is not already registered.
*
@ -4300,6 +4334,14 @@ export var AddonManager = {
return AddonManagerInternal.getAllInstalls();
},
shouldShowBlocklistAttention() {
return AddonManagerInternal.shouldShowBlocklistAttention();
},
getBlocklistAttentionInfo() {
return AddonManagerInternal.getBlocklistAttentionInfo();
},
isInstallEnabled(aType) {
return AddonManagerInternal.isInstallEnabled(aType);
},

View File

@ -1398,6 +1398,22 @@ export var AddonTestUtils = {
});
},
promiseManagerEvent(event, checkFn) {
return new Promise(resolve => {
let listener = {
[event](...args) {
if (typeof checkFn == "function" && !checkFn(...args)) {
return;
}
AddonManager.removeManagerListener(listener);
resolve(args);
},
};
AddonManager.addManagerListener(listener);
});
},
/**
* A helper method to install AddonInstall and wait for completion.
*

View File

@ -200,6 +200,7 @@ const PROP_JSON_FIELDS = [
"requestedPermissions",
"icons",
"iconURL",
"blocklistAttentionDismissed",
"blocklistState",
"blocklistURL",
"startupData",
@ -321,7 +322,8 @@ export class AddonInternal {
this.appDisabled = false;
this.softDisabled = false;
this.embedderDisabled = false;
this.blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
this.blocklistAttentionDismissed = false;
this.blocklistState = nsIBlocklistService.STATE_NOT_BLOCKED;
this.blocklistURL = null;
this.sourceURI = null;
this.releaseNotesURI = null;
@ -672,6 +674,15 @@ export class AddonInternal {
return app;
}
updateBlocklistAttentionDismissed(val) {
if (!this.inDatabase || this.blocklistAttentionDismissed === val) {
return;
}
this.blocklistAttentionDismissed = val;
XPIDatabase.maybeUpdateBlocklistAttentionAddonIdsSet(this);
XPIDatabase.saveChanges();
}
async findBlocklistEntry() {
return lazy.Blocklist.getAddonBlocklistEntry(this.wrapper);
}
@ -688,6 +699,12 @@ export class AddonInternal {
let entry = await this.findBlocklistEntry();
let newState = entry ? entry.state : Services.blocklist.STATE_NOT_BLOCKED;
// Clear the blocklistAttentionDismissed flag if the blocklist state
// is changing.
if (this.blocklistState !== newState) {
this.updateBlocklistAttentionDismissed(false);
}
this.blocklistState = newState;
this.blocklistURL = entry && entry.url;
@ -1266,6 +1283,16 @@ AddonWrapper = class {
return null;
}
get blocklistAttentionDismissed() {
let addon = addonFor(this);
return addon.blocklistAttentionDismissed;
}
set blocklistAttentionDismissed(val) {
let addon = addonFor(this);
addon.updateBlocklistAttentionDismissed(val);
}
updateBlocklistState(applySoftBlock = true) {
return addonFor(this).updateBlocklistState({ applySoftBlock });
}
@ -1814,6 +1841,13 @@ export const XPIDatabase = {
// supported.
orphanedAddons: [],
// Set of the add-on ids for all the add-ons of type extension that are appDisabled or softDisabled
// through the blocklist, excluding the ones that the user has already explicitly dismissed before
// (used for the blocklist attention dot and messagebar to be shown in the extensions button/panel).
//
// Set<addonId: string>
blocklistAttentionAddonIdsSet: new Set(),
_saveTask: null,
// Saved error object if we fail to read an existing database
@ -1991,6 +2025,8 @@ export const XPIDatabase = {
let forEach = this.syncLoadingDB ? arrayForEach : idleForEach;
this.clearBlocklistAttentionAddonIdsSet();
// If we got here, we probably have good data
// Make AddonInternal instances from the loaded data and save them
let addonDB = new Map();
@ -2014,6 +2050,7 @@ export const XPIDatabase = {
let newAddon = new AddonInternal(loadedAddon);
if (loadedAddon.location) {
addonDB.set(newAddon._key, newAddon);
this.maybeUpdateBlocklistAttentionAddonIdsSet(newAddon);
} else {
this.orphanedAddons.push(newAddon);
}
@ -2476,6 +2513,95 @@ export const XPIDatabase = {
);
},
shouldShowBlocklistAttention() {
return !!this.blocklistAttentionAddonIdsSet.size;
},
shouldShowBlocklistAttentionForAddon(addonInternal) {
return (
!addonInternal.hidden &&
!addonInternal.blocklistAttentionDismissed &&
(addonInternal.appDisabled || addonInternal.softDisabled) &&
addonInternal.blocklistState > nsIBlocklistService.STATE_NOT_BLOCKED &&
// We currently only draw the attention of the users when new add-ons of
// type "extension" are being disabled by the blocklist.
addonInternal.type === "extension"
);
},
clearBlocklistAttentionAddonIdsSet() {
this.blocklistAttentionAddonIdsSet.clear();
},
maybeUpdateBlocklistAttentionAddonIdsSet(addonInternal) {
const blocklistAttentionSet = this.blocklistAttentionAddonIdsSet;
if (!this.shouldShowBlocklistAttentionForAddon(addonInternal)) {
blocklistAttentionSet.delete(addonInternal.id);
Services.obs.notifyObservers(
null,
"xpi-provider:blocklist-attention-updated"
);
return;
}
blocklistAttentionSet.add(addonInternal.id);
Services.obs.notifyObservers(
null,
"xpi-provider:blocklist-attention-updated"
);
},
removeFromBlocklistAttentionAddonIdsSet(addonInternal) {
this.blocklistAttentionAddonIdsSet.delete(addonInternal.id);
Services.obs.notifyObservers(
null,
"xpi-provider:blocklist-attention-updated"
);
},
async getBlocklistAttentionInfo() {
const attentionAddonIdsSet = this.blocklistAttentionAddonIdsSet;
const addonFilter = addonInternal =>
attentionAddonIdsSet.has(addonInternal.id) &&
this.shouldShowBlocklistAttentionForAddon(addonInternal);
let addons = attentionAddonIdsSet.size
? await this.getAddonList(addonFilter)
: [];
// Filter the add-ons list once more synchronously in case any change may have happened
// while we were retrieving the add-ons list asynchronously and we may not need to include
// some in the blocklist attention message anymore (e.g. because they have been already
// dismissed, or changed blocklistState or soft-blocked addon being already re-enabled).
addons = addons.filter(addonFilter);
return {
get shouldShow() {
return addons.some(addonFilter);
},
get hasSoftBlocked() {
return addons.some(
addonInternal =>
addonInternal.blocklistState ===
nsIBlocklistService.STATE_SOFTBLOCKED
);
},
get hasHardBlocked() {
return addons.some(
addonInternal =>
addonInternal.blocklistState === nsIBlocklistService.STATE_BLOCKED
);
},
get extensionsCount() {
return addons.length;
},
get addons() {
return addons.map(addonInternal => addonInternal.wrapper);
},
dismiss() {
addons.forEach(addon => addon.updateBlocklistAttentionDismissed(true));
},
};
},
/**
* Synchronously gets all add-ons in the database.
* This is only called from the preference observer for the default
@ -2731,6 +2857,7 @@ export const XPIDatabase = {
removeAddonMetadata(aAddon) {
this.addonDB.delete(aAddon._key);
this.saveChanges();
this.removeFromBlocklistAttentionAddonIdsSet(aAddon);
},
updateXPIStates(addon) {
@ -3009,6 +3136,7 @@ export const XPIDatabase = {
}
this.updateAddonActive(aAddon, !isDisabled);
this.maybeUpdateBlocklistAttentionAddonIdsSet(aAddon);
let bootstrap = XPIExports.XPIInternal.BootstrapScope.get(aAddon);
if (isDisabled) {

View File

@ -119,7 +119,7 @@ const XPI_PERMISSION = "install";
const XPI_SIGNATURE_CHECK_PERIOD = 24 * 60 * 60;
const DB_SCHEMA = 36;
const DB_SCHEMA = 37;
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
@ -3275,6 +3275,14 @@ export var XPIProvider = {
return { addons: result, fullData: false };
},
shouldShowBlocklistAttention() {
return XPIExports.XPIDatabase.shouldShowBlocklistAttention();
},
getBlocklistAttentionInfo() {
return XPIExports.XPIDatabase.getBlocklistAttentionInfo();
},
/*
* Notified when a preference we're interested in has changed.
*

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,8 @@ run-if = ["os == 'android'"]
["test_blocklist_appversion.js"]
skip-if = ["os == 'android' && verify"] # times out
["test_blocklist_attention.js"]
["test_blocklist_clients.js"]
tags = "remote-settings"