Merge autoland to mozilla-central. a=merge

This commit is contained in:
Iulian Moraru 2022-02-27 23:50:00 +02:00
Родитель 92de521c31 484d22abe5
Коммит 684aefab97
22 изменённых файлов: 708 добавлений и 133 удалений

Просмотреть файл

@ -49,14 +49,14 @@
this.tabContainer.init();
this._setupInitialBrowserAndTab();
if (Services.prefs.getBoolPref("browser.display.use_system_colors")) {
this.tabpanels.style.backgroundColor = "-moz-default-background-color";
} else if (
if (
Services.prefs.getIntPref("browser.display.document_color_use") == 2
) {
this.tabpanels.style.backgroundColor = Services.prefs.getCharPref(
"browser.display.background_color"
);
this.tabpanels.style.backgroundColor = Services.prefs.getBoolPref(
"browser.display.use_system_colors"
)
? "canvas"
: Services.prefs.getCharPref("browser.display.background_color");
}
this._setFindbarData();

Просмотреть файл

@ -81,8 +81,7 @@ DisplayPortMargins DisplayPortMargins::ForScrollFrame(
nsIFrame* scrollFrame = do_QueryFrame(aScrollFrame);
PresShell* presShell = scrollFrame->PresShell();
layoutOffset = CSSPoint::FromAppUnits(aScrollFrame->GetScrollPosition());
if (aScrollFrame->IsRootScrollFrameOfDocument() &&
presShell->IsVisualViewportOffsetSet()) {
if (aScrollFrame->IsRootScrollFrameOfDocument()) {
visualOffset =
CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset());

Просмотреть файл

@ -11197,7 +11197,14 @@ bool PresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset,
}
}
nsPoint prevOffset = GetVisualViewportOffset();
// Careful here not to call GetVisualViewportOffset to get the previous visual
// viewport offset because if mVisualViewportOffset is nothing then we'll get
// the layout scroll position directly from the scroll frame and it has likely
// already been updated.
nsPoint prevOffset = aPrevLayoutScrollPos;
if (mVisualViewportOffset.isSome()) {
prevOffset = *mVisualViewportOffset;
}
if (prevOffset == newOffset) {
return false;
}

Просмотреть файл

@ -1481,7 +1481,10 @@ class PresShell final : public nsStubDocumentObserver,
const nsPoint& aPrevLayoutScrollPos);
nsPoint GetVisualViewportOffset() const {
return mVisualViewportOffset.valueOr(nsPoint());
if (mVisualViewportOffset.isSome()) {
return *mVisualViewportOffset;
}
return GetLayoutViewportOffset();
}
bool IsVisualViewportOffsetSet() const {
return mVisualViewportOffset.isSome();

Просмотреть файл

@ -8616,7 +8616,7 @@ ScrollMetadata nsLayoutUtils::ComputeScrollMetadata(
CSSPoint layoutScrollOffset =
CSSPoint::FromAppUnits(scrollableFrame->GetScrollPosition());
CSSPoint visualScrollOffset =
aIsRootContent && presShell->IsVisualViewportOffsetSet()
aIsRootContent
? CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset())
: layoutScrollOffset;
metrics.SetVisualScrollOffset(visualScrollOffset);

Просмотреть файл

@ -4653,9 +4653,7 @@ nsPoint ScrollFrameHelper::GetVisualViewportOffset() const {
if (auto pendingUpdate = presShell->GetPendingVisualScrollUpdate()) {
return pendingUpdate->mVisualScrollOffset;
}
if (presShell->IsVisualViewportOffsetSet()) {
return presShell->GetVisualViewportOffset();
}
return presShell->GetVisualViewportOffset();
}
return GetScrollPosition();
}

Просмотреть файл

@ -636,7 +636,10 @@ var GeckoViewWebExtension = {
async ensureBuiltIn(aUri, aId) {
await AddonManager.readyPromise;
const extensionData = new ExtensionData(aUri);
// Although the add-on is privileged in practice due to it being installed
// as a built-in extension, we pass isPrivileged=false since the exact flag
// doesn't matter as we are only using ExtensionData to read the version.
const extensionData = new ExtensionData(aUri, false);
const [extensionVersion, extension] = await Promise.all([
extensionData.getExtensionVersionWithoutValidation(),
this.extensionById(aId),

Просмотреть файл

@ -530,9 +530,10 @@ const manifestTypes = new Map([
* `loadManifest` has been called, and completed.
*/
class ExtensionData {
constructor(rootURI) {
constructor(rootURI, isPrivileged = false) {
this.rootURI = rootURI;
this.resourceURL = rootURI.spec;
this.isPrivileged = isPrivileged;
this.manifest = null;
this.type = null;
@ -553,6 +554,38 @@ class ExtensionData {
this.eventPagesEnabled = eventPagesEnabled;
}
/**
* A factory function that allows the construction of ExtensionData, with
* the isPrivileged flag computed asynchronously.
*
* @param {nsIURI} rootURI
* The URI pointing to the extension root.
* @param {function(type, id)} checkPrivileged
* An (async) function that takes the addon type and addon ID and returns
* whether the given add-on is privileged.
* @returns {ExtensionData}
*/
static async constructAsync({ rootURI, checkPrivileged }) {
let extension = new ExtensionData(rootURI);
// checkPrivileged depends on the extension type and id.
await extension.initializeAddonTypeAndID();
let { type, id } = extension;
// Map the extension type to the type name used by the add-on manager.
// TODO bug 1757084: Remove this.
type = type == "langpack" ? "locale" : type;
extension.isPrivileged = await checkPrivileged(type, id);
return extension;
}
static getIsPrivileged({ signedState, builtIn, temporarilyInstalled }) {
return (
signedState === AddonManager.SIGNEDSTATE_PRIVILEGED ||
signedState === AddonManager.SIGNEDSTATE_SYSTEM ||
builtIn ||
(AddonSettings.EXPERIMENTS_ENABLED && temporarilyInstalled)
);
}
get builtinMessages() {
return null;
}
@ -725,25 +758,8 @@ class ExtensionData {
});
}
canCheckSignature() {
// ExtensionData instances can't check the signature because it is not yet
// available when XPIProvider does use it to load the extension manifest.
//
// This method will return true for the ExtensionData subclasses (like
// the Extension class) to enable the additional validation that would require
// the signature to be available (e.g. to check if the extension is allowed to
// use a privileged permission).
return this.constructor != ExtensionData;
}
get restrictSchemes() {
// mozillaAddons permission is only allowed for privileged addons and
// filtered out if the extension isn't privileged.
// When the manifest is loaded by an explicit ExtensionData class
// instance, the signature data isn't available yet and this helper
// would always return false, but it will return true when appropriate
// (based on the isPrivileged boolean property) for the Extension class.
return !this.hasPermission("mozillaAddons");
return !(this.isPrivileged && this.hasPermission("mozillaAddons"));
}
/**
@ -1027,17 +1043,50 @@ class ExtensionData {
return Schemas.normalize(this.rawManifest, manifestType, context);
}
async initializeAddonTypeAndID() {
if (this.type) {
// Already initialized.
return;
}
this.rawManifest = await this.readJSON("manifest.json");
let manifest = this.rawManifest;
if (manifest.theme) {
this.type = "theme";
} else if (manifest.langpack_id) {
// TODO bug 1757084: This should be "locale".
this.type = "langpack";
} else if (manifest.dictionaries) {
this.type = "dictionary";
} else if (manifest.site_permissions) {
this.type = "sitepermission";
} else {
this.type = "extension";
}
if (!this.id) {
let bss =
manifest.browser_specific_settings?.gecko ||
manifest.applications?.gecko;
let id = bss?.id;
// This is a basic type check.
// When parseManifest is called, the ID is validated more thoroughly
// because the id is defined to be an ExtensionID type in
// toolkit/components/extensions/schemas/manifest.json
if (typeof id == "string") {
this.id = id;
}
}
}
// eslint-disable-next-line complexity
async parseManifest() {
let [manifest] = await Promise.all([
this.readJSON("manifest.json"),
Management.lazyInit(),
]);
await Promise.all([this.initializeAddonTypeAndID(), Management.lazyInit()]);
let manifest = this.rawManifest;
this.manifest = manifest;
this.rawManifest = manifest;
if (manifest && manifest.default_locale) {
if (manifest.default_locale) {
await this.initLocale();
}
@ -1045,7 +1094,7 @@ class ExtensionData {
// have isPrivileged, so ignore fluent localization in that pass.
// This means that fluent cannot be used to localize manifest properties
// read from the add-on manager (e.g., author, homepage, etc.)
if (manifest && manifest.l10n_resources && "isPrivileged" in this) {
if (manifest.l10n_resources && this.constructor != ExtensionData) {
if (this.isPrivileged) {
this.fluentL10n = new Localization(manifest.l10n_resources, true);
} else {
@ -1054,18 +1103,6 @@ class ExtensionData {
}
}
if (this.manifest.theme) {
this.type = "theme";
} else if (this.manifest.langpack_id) {
this.type = "langpack";
} else if (this.manifest.dictionaries) {
this.type = "dictionary";
} else if (this.manifest.site_permissions) {
this.type = "sitepermission";
} else {
this.type = "extension";
}
let normalized = await this._getNormalizedManifest();
if (normalized.error) {
this.manifestError(normalized.error);
@ -1096,8 +1133,6 @@ class ExtensionData {
this.logWarning("Event pages are not currently supported.");
}
this.id ??= manifest.applications?.gecko?.id;
let apiNames = new Set();
let dependencies = new Set();
let originPermissions = new Set();
@ -1106,6 +1141,9 @@ class ExtensionData {
let schemaPromises = new Map();
// Note: this.id and this.type were computed in initializeAddonTypeAndID.
// The format of `this.id` was confirmed to be a valid extensionID by the
// Schema validation as part of the _getNormalizedManifest() call.
let result = {
apiNames,
dependencies,
@ -1148,18 +1186,6 @@ class ExtensionData {
} else if (type.api) {
apiNames.add(type.api);
} else if (type.invalid) {
if (!this.canCheckSignature() && PRIVILEGED_PERMS.has(perm)) {
// Do not emit the warning if the invalid permission is a privileged one
// and the current instance can't yet check for a valid signature
// (see Bug 1675858 and the inline comment inside the canCheckSignature
// method for more details).
//
// This parseManifest method will be called again on the Extension class
// instance, which will have the signature available and the invalid
// extension permission warnings will be collected and logged if necessary.
continue;
}
this.manifestWarning(`Invalid extension permission: ${perm}`);
continue;
}
@ -1999,6 +2025,7 @@ class BootstrapScope {
return Management.emit("update", {
id: data.id,
resourceURI: data.resourceURI,
isPrivileged: data.isPrivileged,
});
}
@ -2101,7 +2128,7 @@ let pendingExtensions = new Map();
*/
class Extension extends ExtensionData {
constructor(addonData, startupReason) {
super(addonData.resourceURI);
super(addonData.resourceURI, addonData.isPrivileged);
this.startupStates = new Set();
this.state = "Not started";
@ -2345,15 +2372,6 @@ class Extension extends ExtensionData {
return [this.id, this.version, Services.locale.appLocaleAsBCP47];
}
get isPrivileged() {
return (
this.addonData.signedState === AddonManager.SIGNEDSTATE_PRIVILEGED ||
this.addonData.signedState === AddonManager.SIGNEDSTATE_SYSTEM ||
this.addonData.builtIn ||
(AddonSettings.EXPERIMENTS_ENABLED && this.temporarilyInstalled)
);
}
get temporarilyInstalled() {
return !!this.addonData.temporarilyInstalled;
}

Просмотреть файл

@ -117,13 +117,13 @@ let apiManager = new (class extends SchemaAPIManager {
return extension.apiManager.onStartup(extension);
});
this.on("update", async (e, { id, resourceURI }) => {
this.on("update", async (e, { id, resourceURI, isPrivileged }) => {
let modules = this.eventModules.get("update");
if (modules.size == 0) {
return;
}
let extension = new ExtensionData(resourceURI);
let extension = new ExtensionData(resourceURI, isPrivileged);
await extension.loadManifest();
return Promise.all(

Просмотреть файл

@ -36,6 +36,11 @@ ChromeUtils.defineModuleGetter(
"Extension",
"resource://gre/modules/Extension.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"ExtensionData",
"resource://gre/modules/Extension.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"ExtensionParent",
@ -575,6 +580,12 @@ ExtensionTestCommon = class ExtensionTestCommon {
signedState = AddonManager.SIGNEDSTATE_SYSTEM;
}
let isPrivileged = ExtensionData.getIsPrivileged({
signedState,
builtIn: false,
temporarilyInstalled: !!data.temporarilyInstalled,
});
return new Extension(
{
id,
@ -583,6 +594,7 @@ ExtensionTestCommon = class ExtensionTestCommon {
signedState,
incognitoOverride: data.incognitoOverride,
temporarilyInstalled: !!data.temporarilyInstalled,
isPrivileged,
TEST_NO_ADDON_MANAGER: true,
// By default we set TEST_NO_DELAYED_STARTUP to true
TEST_NO_DELAYED_STARTUP: !data.delayedStartup,

Просмотреть файл

@ -27,7 +27,7 @@ add_task(async function test_json_parser() {
let fileURI = Services.io.newFileURI(xpi);
let uri = NetUtil.newURI(`jar:${fileURI.spec}!/`);
let extension = new ExtensionData(uri);
let extension = new ExtensionData(uri, false);
await extension.parseManifest();
@ -51,7 +51,7 @@ add_task(async function test_getExtensionVersionWithoutValidation() {
});
let fileURI = Services.io.newFileURI(xpi);
let uri = NetUtil.newURI(`jar:${fileURI.spec}!/`);
let extension = new ExtensionData(uri);
let extension = new ExtensionData(uri, false);
let rawVersion = await extension.getExtensionVersionWithoutValidation();
Assert.deepEqual(

Просмотреть файл

@ -668,26 +668,28 @@ add_task(async function update_unprivileged_with_mozillaAddons() {
});
// Tests that invalid permission warning for privileged permissions requested
// without the privilged signature are emitted by the Extension class instance
// but not for the ExtensionData instances (on which the signature is not
// available and the warning would be emitted even for the ones signed correctly).
// are not emitted for privileged extensions, only for unprivileged extensions.
add_task(
async function test_invalid_permission_warning_on_privileged_permission() {
await AddonTestUtils.promiseStartupManager();
const MANIFEST_WARNINGS = [
"Reading manifest: Invalid extension permission: mozillaAddons",
"Reading manifest: Invalid extension permission: resource://x/",
"Reading manifest: Invalid extension permission: about:reader*",
];
async function testInvalidPermissionWarning({ isPrivileged }) {
let id = isPrivileged
? "privileged-addon@mochi.test"
: "nonprivileged-addon@mochi.test";
let expectedWarnings = isPrivileged
? []
: ["Reading manifest: Invalid extension permission: mozillaAddons"];
let expectedWarnings = isPrivileged ? [] : MANIFEST_WARNINGS;
const ext = ExtensionTestUtils.loadExtension({
useAddonManager: "permanent",
manifest: {
permissions: ["mozillaAddons"],
permissions: ["mozillaAddons", "resource://x/", "about:reader*"],
applications: { gecko: { id } },
},
background() {},
@ -711,28 +713,67 @@ add_task(
// ExtensionData instance created below).
let generatedExt = ExtensionTestCommon.generate({
manifest: {
permissions: ["mozillaAddons"],
permissions: ["mozillaAddons", "resource://x/", "about:reader*"],
applications: { gecko: { id: "extension-data@mochi.test" } },
},
});
// Verify that XPIInstall.jsm will not collect the warning for the
// privileged permission as expected.
const extData = new ExtensionData(generatedExt.rootURI);
await extData.loadManifest();
async function getWarningsFromExtensionData({ isPrivileged }) {
let extData;
if (typeof isPrivileged == "function") {
// isPrivileged expected to be computed asynchronously.
extData = await ExtensionData.constructAsync({
rootURI: generatedExt.rootURI,
checkPrivileged: isPrivileged,
});
} else {
extData = new ExtensionData(generatedExt.rootURI, isPrivileged);
}
await extData.loadManifest();
// This assertion is just meant to prevent the test to pass if there were
// no warnings because some errors prevented the warnings to be
// collected).
Assert.deepEqual(
extData.errors,
[],
"No errors collected by the ExtensionData instance"
);
return extData.warnings;
}
Assert.deepEqual(
extData.warnings,
[],
"No warnings for mozillaAddons permission collected for the ExtensionData instance"
await getWarningsFromExtensionData({ isPrivileged: undefined }),
MANIFEST_WARNINGS,
"Got warnings about privileged permissions by default"
);
// This assertion is just meant to prevent the test to pass if there were no warnings
// because some errors prevented the warnings to be collected).
Assert.deepEqual(
extData.errors,
[],
"No errors collected by the ExtensionData instance"
await getWarningsFromExtensionData({ isPrivileged: false }),
MANIFEST_WARNINGS,
"Got warnings about privileged permissions for non-privileged extensions"
);
Assert.deepEqual(
await getWarningsFromExtensionData({ isPrivileged: true }),
[],
"No warnings about privileged permissions on privileged extensions"
);
Assert.deepEqual(
await getWarningsFromExtensionData({ isPrivileged: async () => false }),
MANIFEST_WARNINGS,
"Got warnings about privileged permissions for non-privileged extensions (async)"
);
Assert.deepEqual(
await getWarningsFromExtensionData({ isPrivileged: async () => true }),
[],
"No warnings about privileged permissions on privileged extensions (async)"
);
// Cleanup the generated xpi file.
await generatedExt.cleanupGeneratedFile();

Просмотреть файл

@ -19,7 +19,7 @@ async function generateAddon(data) {
let fileURI = Services.io.newFileURI(xpi);
let jarURI = NetUtil.newURI(`jar:${fileURI.spec}!/`);
let extension = new ExtensionData(jarURI);
let extension = new ExtensionData(jarURI, false);
await extension.loadManifest();
return extension;

Просмотреть файл

@ -49,7 +49,7 @@ add_task(async function test_validate_manifest() {
for (const xpi of searchExtensions) {
info(`loading: ${SEARCH_EXTENSIONS_PATH}/${xpi}/`);
let fileURI = getFileURI(`${SEARCH_EXTENSIONS_PATH}/${xpi}/`);
let extension = new ExtensionData(fileURI);
let extension = new ExtensionData(fileURI, false);
await extension.loadManifest();
let locales = await extension.promiseLocales();
for (let locale of locales.keys()) {

Просмотреть файл

@ -26,6 +26,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm",
AddonSettings: "resource://gre/modules/addons/AddonSettings.jsm",
DeferredTask: "resource://gre/modules/DeferredTask.jsm",
ExtensionData: "resource://gre/modules/Extension.jsm",
ExtensionUtils: "resource://gre/modules/ExtensionUtils.jsm",
FileUtils: "resource://gre/modules/FileUtils.jsm",
PermissionsUtils: "resource://gre/modules/PermissionsUtils.jsm",
@ -480,17 +481,22 @@ class AddonInternal {
return this.isCompatibleWith();
}
// This matches Extension.isPrivileged with the exception of temporarily installed extensions.
get isPrivileged() {
return (
this.signedState === AddonManager.SIGNEDSTATE_PRIVILEGED ||
this.signedState === AddonManager.SIGNEDSTATE_SYSTEM ||
this.location.isBuiltin
);
return ExtensionData.getIsPrivileged({
signedState: this.signedState,
builtIn: this.location.isBuiltin,
temporarilyInstalled: this.location.isTemporary,
});
}
get hidden() {
return this.location.hidden || (this._hidden && this.isPrivileged) || false;
return (
this.location.hidden ||
// The hidden flag is intended to only be used for features that are part
// of the application. Temporary add-ons should not be hidden.
(this._hidden && this.isPrivileged && !this.location.isTemporary) ||
false
);
}
set hidden(val) {

Просмотреть файл

@ -241,8 +241,8 @@ class Package {
return new TextDecoder().decode(buffer);
}
async verifySignedState(addon) {
if (!shouldVerifySignedState(addon)) {
async verifySignedState(addonId, addonType, addonLocation) {
if (!shouldVerifySignedState(addonType, addonLocation)) {
return {
signedState: AddonManager.SIGNEDSTATE_NOT_REQUIRED,
cert: null,
@ -257,7 +257,7 @@ class Package {
root = Ci.nsIX509CertDB.AddonsStageRoot;
}
return this.verifySignedStateForRoot(addon, root);
return this.verifySignedStateForRoot(addonId, root);
}
flushCache() {}
@ -308,7 +308,7 @@ DirPackage = class DirPackage extends Package {
return IOUtils.read(PathUtils.join(this.filePath, ...path));
}
async verifySignedStateForRoot(addon, root) {
async verifySignedStateForRoot(addonId, root) {
return { signedState: AddonManager.SIGNEDSTATE_UNKNOWN, cert: null };
}
};
@ -345,7 +345,7 @@ XPIPackage = class XPIPackage extends Package {
return response.arrayBuffer();
}
verifySignedStateForRoot(addon, root) {
verifySignedStateForRoot(addonId, root) {
return new Promise(resolve => {
let callback = {
openSignedAppFileFinished(aRv, aZipReader, aCert) {
@ -353,7 +353,7 @@ XPIPackage = class XPIPackage extends Package {
aZipReader.close();
}
resolve({
signedState: getSignedStatus(aRv, aCert, addon.id),
signedState: getSignedStatus(aRv, aCert, addonId),
cert: aCert,
});
},
@ -441,14 +441,30 @@ function waitForAllPromises(promises) {
*
* @param {Package} aPackage
* The install package for the add-on
* @returns {AddonInternal}
* @param {XPIStateLocation} aLocation
* The install location the add-on is installed in, or will be
* installed to.
* @returns {{ addon: AddonInternal, verifiedSignedState: object}}
* @throws if the install manifest in the stream is corrupt or could not
* be read
*/
async function loadManifestFromWebManifest(aPackage) {
let extension = new ExtensionData(
XPIInternal.maybeResolveURI(aPackage.rootURI)
);
async function loadManifestFromWebManifest(aPackage, aLocation) {
let verifiedSignedState;
let extension = await ExtensionData.constructAsync({
rootURI: XPIInternal.maybeResolveURI(aPackage.rootURI),
async checkPrivileged(type, id) {
verifiedSignedState = await aPackage.verifySignedState(
id,
type,
aLocation
);
return ExtensionData.getIsPrivileged({
signedState: verifiedSignedState.signedState,
builtIn: aLocation.isBuiltin,
temporarilyInstalled: aLocation.isTemporary,
});
},
});
let manifest = await extension.loadManifest();
@ -489,7 +505,7 @@ async function loadManifestFromWebManifest(aPackage) {
addon.aboutURL = null;
addon.dependencies = Object.freeze(Array.from(extension.dependencies));
addon.startupData = extension.startupData;
addon.hidden = manifest.hidden;
addon.hidden = extension.isPrivileged && manifest.hidden;
addon.incognito = manifest.incognito;
if (addon.type === "theme" && (await aPackage.hasResource("preview.png"))) {
@ -574,7 +590,7 @@ async function loadManifestFromWebManifest(aPackage) {
addon.softDisabled =
addon.blocklistState == nsIBlocklistService.STATE_SOFTBLOCKED;
return addon;
return { addon, verifiedSignedState };
}
async function readRecommendationStates(aPackage, aAddonID) {
@ -649,13 +665,23 @@ function generateTemporaryInstallID(aFile) {
var loadManifest = async function(aPackage, aLocation, aOldAddon) {
let addon;
let verifiedSignedState;
if (await aPackage.hasResource("manifest.json")) {
addon = await loadManifestFromWebManifest(aPackage);
({ addon, verifiedSignedState } = await loadManifestFromWebManifest(
aPackage,
aLocation
));
} else {
// TODO bug 1674799: Remove this unused branch.
for (let loader of AddonManagerPrivate.externalExtensionLoaders.values()) {
if (await aPackage.hasResource(loader.manifestFile)) {
addon = await loader.loadManifest(aPackage);
addon.loader = loader.name;
verifiedSignedState = await aPackage.verifySignedState(
addon.id,
addon.type,
aLocation
);
break;
}
}
@ -671,12 +697,9 @@ var loadManifest = async function(aPackage, aLocation, aOldAddon) {
addon.rootURI = aPackage.rootURI.spec;
addon.location = aLocation;
let { signedState, cert } = await aPackage.verifySignedState(addon);
let { signedState, cert } = verifiedSignedState;
addon.signedState = signedState;
addon.signedDate = cert?.validity?.notBefore / 1000 || null;
if (!addon.isPrivileged) {
addon.hidden = false;
}
if (!addon.id) {
if (cert) {
@ -865,7 +888,7 @@ function getSignedStatus(aRv, aCert, aAddonID) {
}
}
function shouldVerifySignedState(aAddon) {
function shouldVerifySignedState(aAddonType, aLocation) {
// TODO when KEY_APP_SYSTEM_DEFAULTS and KEY_APP_SYSTEM_ADDONS locations
// are removed, we need to reorganize the logic here. At that point we
// should:
@ -874,25 +897,25 @@ function shouldVerifySignedState(aAddon) {
// return SIGNED_TYPES.has(type)
// We don't care about signatures for default system add-ons
if (aAddon.location.name == KEY_APP_SYSTEM_DEFAULTS) {
if (aLocation.name == KEY_APP_SYSTEM_DEFAULTS) {
return false;
}
// Updated system add-ons should always have their signature checked
if (aAddon.location.isSystem) {
if (aLocation.isSystem) {
return true;
}
if (
aAddon.location.isBuiltin ||
aAddon.location.scope & AppConstants.MOZ_UNSIGNED_SCOPES
aLocation.isBuiltin ||
aLocation.scope & AppConstants.MOZ_UNSIGNED_SCOPES
) {
return false;
}
// Otherwise only check signatures if the add-on is one of the signed
// types.
return XPIDatabase.SIGNED_TYPES.has(aAddon.type);
return XPIDatabase.SIGNED_TYPES.has(aAddonType);
}
/**
@ -909,7 +932,11 @@ function shouldVerifySignedState(aAddon) {
var verifyBundleSignedState = async function(aBundle, aAddon) {
let pkg = Package.get(aBundle);
try {
let { signedState } = await pkg.verifySignedState(aAddon);
let { signedState } = await pkg.verifySignedState(
aAddon.id,
aAddon.type,
aAddon.location
);
return signedState;
} finally {
pkg.close();

Просмотреть файл

@ -33,6 +33,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
Dictionary: "resource://gre/modules/Extension.jsm",
Extension: "resource://gre/modules/Extension.jsm",
ExtensionData: "resource://gre/modules/Extension.jsm",
Langpack: "resource://gre/modules/Extension.jsm",
SitePermission: "resource://gre/modules/Extension.jsm",
FileUtils: "resource://gre/modules/FileUtils.jsm",
@ -565,6 +566,14 @@ class XPIState {
return this.loader == null;
}
get isPrivileged() {
return ExtensionData.getIsPrivileged({
signedState: this.signedState,
builtIn: this.location.isBuiltin,
temporarilyInstalled: this.location.isTemporary,
});
}
/**
* Update the last modified time for an add-on on disk.
*
@ -1813,6 +1822,7 @@ class BootstrapScope {
temporarilyInstalled: addon.location.isTemporary,
builtIn: addon.location.isBuiltin,
isSystem: addon.location.isSystem,
isPrivileged: addon.isPrivileged,
recommendationState: addon.recommendationState,
};

Просмотреть файл

@ -1,3 +1,8 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function test_hidden() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "2");
AddonTestUtils.usePrivilegedSignatures = id => id.startsWith("privileged");
@ -54,5 +59,18 @@ add_task(async function test_hidden() {
ok(!addon2.isPrivileged, "Unprivileged extension is not privileged");
ok(!addon2.hidden, "Unprivileged extension should not be hidden");
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {
applications: { gecko: { id: "privileged@but-temporary" } },
hidden: true,
},
});
await extension.startup();
let tempAddon = extension.addon;
ok(tempAddon.isPrivileged, "Temporary add-on is privileged");
ok(!tempAddon.hidden, "Temporary add-on is not hidden despite privilige");
await extension.unload();
await promiseShutdownManager();
});

Просмотреть файл

@ -0,0 +1,190 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { XPIInstall } = ChromeUtils.import(
"resource://gre/modules/addons/XPIInstall.jsm"
);
const {
XPIInternal: {
BuiltInLocation,
KEY_APP_PROFILE,
KEY_APP_SYSTEM_DEFAULTS,
KEY_APP_SYSTEM_PROFILE,
TemporaryInstallLocation,
XPIStates,
},
} = ChromeUtils.import("resource://gre/modules/addons/XPIProvider.jsm");
AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
Services.prefs.setIntPref(
"extensions.enabledScopes",
// SCOPE_PROFILE is enabled by default,
// SCOPE_APPLICATION is to enable KEY_APP_SYSTEM_PROFILE, which we need to
// test the combination (isSystem && !isBuiltin) in test_system_location.
AddonManager.SCOPE_PROFILE | AddonManager.SCOPE_APPLICATION
);
// test_builtin_system_location tests the (isSystem && isBuiltin) combination
// (i.e. KEY_APP_SYSTEM_DEFAULTS). That location only exists if this directory
// is found:
const distroDir = FileUtils.getDir("ProfD", ["sysfeatures"], true);
registerDirectory("XREAppFeat", distroDir);
function getInstallLocation({
isBuiltin = false,
isSystem = false,
isTemporary = false,
}) {
if (isTemporary) {
// Temporary installation. Signatures will not be verified.
return TemporaryInstallLocation; // KEY_APP_TEMPORARY
}
let location;
if (isSystem) {
if (isBuiltin) {
// System location. Signatures will not be verified.
location = XPIStates.getLocation(KEY_APP_SYSTEM_DEFAULTS);
} else {
// Normandy installations. Signatures will be verified.
location = XPIStates.getLocation(KEY_APP_SYSTEM_PROFILE);
}
} else if (isBuiltin) {
// Packaged with the application. Signatures will not be verified.
location = BuiltInLocation; // KEY_APP_BUILTINS
} else {
// By default - The profile directory. Signatures will be verified.
location = XPIStates.getLocation(KEY_APP_PROFILE);
}
// Sanity checks to make sure that the flags match the expected values.
if (location.isSystem !== isSystem) {
ok(false, `${location.name}, unexpected isSystem=${location.isSystem}`);
}
if (location.isBuiltin !== isBuiltin) {
ok(false, `${location.name}, unexpected isBuiltin=${location.isBuiltin}`);
}
return location;
}
async function testLoadManifest({ location, expectPrivileged }) {
location ??= getInstallLocation({});
let xpi = await AddonTestUtils.createTempWebExtensionFile({
manifest: {
applications: { gecko: { id: "@with-privileged-perm" } },
permissions: ["mozillaAddons", "cookies"],
},
});
let actualPermissions;
let { messages } = await AddonTestUtils.promiseConsoleOutput(async () => {
let addon = await XPIInstall.loadManifestFromFile(xpi, location);
actualPermissions = addon.userPermissions;
equal(addon.isPrivileged, expectPrivileged, "addon.isPrivileged");
});
if (expectPrivileged) {
AddonTestUtils.checkMessages(messages, {
expected: [],
forbidden: [
{
message: /Reading manifest: Invalid extension permission/,
},
],
});
Assert.deepEqual(
actualPermissions,
{ origins: [], permissions: ["mozillaAddons", "cookies"] },
"Privileged permission should exist"
);
} else {
AddonTestUtils.checkMessages(messages, {
expected: [
{
message: /Reading manifest: Invalid extension permission: mozillaAddons/,
},
],
forbidden: [],
});
Assert.deepEqual(
actualPermissions,
{ origins: [], permissions: ["cookies"] },
"Privileged permission should be ignored"
);
}
}
add_task(async function setup() {
await ExtensionTestUtils.startAddonManager();
});
add_task(async function test_regular_addon() {
AddonTestUtils.usePrivilegedSignatures = false;
await testLoadManifest({
expectPrivileged: false,
});
});
add_task(async function test_privileged_signature() {
AddonTestUtils.usePrivilegedSignatures = true;
await testLoadManifest({
expectPrivileged: true,
});
});
add_task(async function test_system_signature() {
AddonTestUtils.usePrivilegedSignatures = "system";
await testLoadManifest({
expectPrivileged: true,
});
});
add_task(async function test_builtin_location() {
AddonTestUtils.usePrivilegedSignatures = false;
await testLoadManifest({
expectPrivileged: true,
location: getInstallLocation({ isBuiltin: true }),
});
});
add_task(async function test_system_location() {
AddonTestUtils.usePrivilegedSignatures = false;
await testLoadManifest({
expectPrivileged: false,
location: getInstallLocation({ isSystem: true }),
});
});
add_task(async function test_builtin_system_location() {
AddonTestUtils.usePrivilegedSignatures = false;
await testLoadManifest({
expectPrivileged: true,
location: getInstallLocation({ isSystem: true, isBuiltin: true }),
});
});
add_task(async function test_temporary_regular() {
AddonTestUtils.usePrivilegedSignatures = false;
Services.prefs.setBoolPref("extensions.experiments.enabled", false);
await testLoadManifest({
expectPrivileged: false,
location: getInstallLocation({ isTemporary: true }),
});
});
add_task(async function test_temporary_privileged_signature() {
AddonTestUtils.usePrivilegedSignatures = true;
Services.prefs.setBoolPref("extensions.experiments.enabled", false);
await testLoadManifest({
expectPrivileged: true,
location: getInstallLocation({ isTemporary: true }),
});
});
add_task(async function test_temporary_experiments_enabled() {
AddonTestUtils.usePrivilegedSignatures = false;
Services.prefs.setBoolPref("extensions.experiments.enabled", true);
await testLoadManifest({
expectPrivileged: true,
location: getInstallLocation({ isTemporary: true }),
});
});

Просмотреть файл

@ -0,0 +1,58 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const ADDON_ID_PRIVILEGED = "@privileged-addon-id";
const ADDON_ID_NO_PRIV = "@addon-without-privileges";
AddonTestUtils.usePrivilegedSignatures = id => id === ADDON_ID_PRIVILEGED;
function isExtensionPrivileged(addonId) {
const { extension } = WebExtensionPolicy.getByID(addonId);
return extension.isPrivileged;
}
add_task(async function setup() {
await ExtensionTestUtils.startAddonManager();
});
add_task(async function isPrivileged_at_install() {
{
let addon = await promiseInstallWebExtension({
manifest: {
permissions: ["mozillaAddons"],
applications: { gecko: { id: ADDON_ID_PRIVILEGED } },
},
});
ok(addon.isPrivileged, "Add-on is privileged");
ok(isExtensionPrivileged(ADDON_ID_PRIVILEGED), "Extension is privileged");
}
{
let addon = await promiseInstallWebExtension({
manifest: {
permissions: ["mozillaAddons"],
applications: { gecko: { id: ADDON_ID_NO_PRIV } },
},
});
ok(!addon.isPrivileged, "Add-on is not privileged");
ok(!isExtensionPrivileged(ADDON_ID_NO_PRIV), "Extension is not privileged");
}
});
// When the Add-on Manager is restarted, the extension is started using data
// from XPIState. This test verifies that `extension.isPrivileged` is correctly
// set in that scenario.
add_task(async function isPrivileged_at_restart() {
await promiseRestartManager();
{
let addon = await AddonManager.getAddonByID(ADDON_ID_PRIVILEGED);
ok(addon.isPrivileged, "Add-on is privileged");
ok(isExtensionPrivileged(ADDON_ID_PRIVILEGED), "Extension is privileged");
}
{
let addon = await AddonManager.getAddonByID(ADDON_ID_NO_PRIV);
ok(!addon.isPrivileged, "Add-on is not privileged");
ok(!isExtensionPrivileged(ADDON_ID_NO_PRIV), "Extension is not privileged");
}
});

Просмотреть файл

@ -0,0 +1,182 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
ChromeUtils.defineModuleGetter(
this,
"ExtensionParent",
"resource://gre/modules/ExtensionParent.jsm"
);
AddonTestUtils.usePrivilegedSignatures = id => id === "privileged@ext";
const EXTENSION_API_IMPL = `
this.extensionApiImpl = class extends ExtensionAPI {
onStartup() {
extensions.emit("test-ExtensionAPI-onStartup", {
extensionId: this.extension.id,
version: this.extension.manifest.version,
});
}
static onUpdate(id, manifest) {
extensions.emit("test-ExtensionAPI-onUpdate", {
extensionId: id,
version: manifest.version,
});
}
};`;
function setupTestExtensionAPI() {
// The EXTENSION_API_IMPL script is going to be loaded in the main process,
// where only safe loads are permitted. So we generate a resource:-URL, to
// avoid the use of security.allow_parent_unrestricted_js_loads.
let resProto = Services.io
.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
resProto.setSubstitution(
"extensionApiImplJs",
Services.io.newURI(`data:,${encodeURIComponent(EXTENSION_API_IMPL)}`)
);
registerCleanupFunction(() => {
resProto.setSubstitution("extensionApiImplJs", null);
});
const modules = {
extensionApiImpl: {
url: "resource://extensionApiImplJs",
events: ["startup", "update"],
},
};
Services.catMan.addCategoryEntry(
"webextension-modules",
"test-register-extensionApiImpl",
`data:,${JSON.stringify(modules)}`,
false,
false
);
}
async function runInstallAndUpdate({
extensionId,
expectPrivileged,
installExtensionData,
}) {
let extensionData = {
useAddonManager: "permanent",
manifest: {
applications: { gecko: { id: extensionId } },
version: "1.1",
},
};
let events = [];
function onUpdated(type, params) {
params = { type, ...params };
// resourceURI cannot be serialized for use with deepEqual.
delete params.resourceURI;
events.push(params);
}
function onExtensionAPI(type, params) {
events.push({ type, ...params });
}
ExtensionParent.apiManager.on("update", onUpdated);
ExtensionParent.apiManager.on("test-ExtensionAPI-onStartup", onExtensionAPI);
ExtensionParent.apiManager.on("test-ExtensionAPI-onUpdate", onExtensionAPI);
let { addon } = await installExtensionData(extensionData);
equal(addon.isPrivileged, expectPrivileged, "Expected isPrivileged");
extensionData.manifest.version = "2.22";
extensionData.manifest.permissions = ["mozillaAddons"];
// May warn about invalid permissions when the extension is not privileged.
ExtensionTestUtils.failOnSchemaWarnings(false);
let extension = await installExtensionData(extensionData);
ExtensionTestUtils.failOnSchemaWarnings(true);
await extension.unload();
ExtensionParent.apiManager.off("update", onUpdated);
ExtensionParent.apiManager.off("test-ExtensionAPI-onStartup", onExtensionAPI);
ExtensionParent.apiManager.off("test-ExtensionAPI-onUpdate", onExtensionAPI);
// Verify that we have (1) installed and (2) updated the extension.
Assert.deepEqual(
events,
[
{ type: "test-ExtensionAPI-onStartup", extensionId, version: "1.1" },
// The next two events show that ExtensionParent has run the onUpdate
// handler, during which ExtensionData has supposedly been constructed.
{ type: "update", id: extensionId, isPrivileged: expectPrivileged },
{ type: "test-ExtensionAPI-onUpdate", extensionId, version: "2.22" },
{ type: "test-ExtensionAPI-onStartup", extensionId, version: "2.22" },
],
"Expected startup and update events"
);
}
add_task(async function setup() {
setupTestExtensionAPI();
await ExtensionTestUtils.startAddonManager();
});
// Tests that privileged extensions (e.g builtins) are always parsed with the
// correct isPrivileged flag.
add_task(async function test_install_and_update_builtin() {
let { messages } = await promiseConsoleOutput(async () => {
await runInstallAndUpdate({
extensionId: "builtin@ext",
expectPrivileged: true,
async installExtensionData(extData) {
return installBuiltinExtension(extData);
},
});
});
AddonTestUtils.checkMessages(messages, {
expected: [{ message: /Addon with ID builtin@ext already installed,/ }],
forbidden: [{ message: /Invalid extension permission: mozillaAddons/ }],
});
});
add_task(async function test_install_and_update_regular_ext() {
let { messages } = await promiseConsoleOutput(async () => {
await runInstallAndUpdate({
extensionId: "regular@ext",
expectPrivileged: false,
async installExtensionData(extData) {
let extension = ExtensionTestUtils.loadExtension(extData);
await extension.startup();
return extension;
},
});
});
let errPattern = /Loading extension 'regular@ext': Reading manifest: Invalid extension permission: mozillaAddons/;
let permissionWarnings = messages.filter(msg => errPattern.test(msg.message));
// Expected number of warnings after triggering the update:
// 1. Generated when the loaded by the Addons manager (ExtensionData).
// 2. Generated when read again before ExtensionAPI.onUpdate (ExtensionData).
// 3. Generated when the extension actually runs (Extension).
equal(permissionWarnings.length, 3, "Expected number of permission warnings");
});
add_task(async function test_install_and_update_privileged_ext() {
let { messages } = await promiseConsoleOutput(async () => {
await runInstallAndUpdate({
extensionId: "privileged@ext",
expectPrivileged: true,
async installExtensionData(extData) {
let extension = ExtensionTestUtils.loadExtension(extData);
await extension.startup();
return extension;
},
});
});
AddonTestUtils.checkMessages(messages, {
expected: [
// First installation.
{ message: /Starting install of privileged@ext / },
// Second installation (update).
{ message: /Starting install of privileged@ext / },
],
forbidden: [{ message: /Invalid extension permission: mozillaAddons/ }],
});
});

Просмотреть файл

@ -57,6 +57,7 @@ skip-if = appname != "firefox" || (os == "win" && processor == "aarch64") # bug
[test_installtrigger_schemes.js]
[test_isDebuggable.js]
[test_isReady.js]
[test_loadManifest_isPrivileged.js]
[test_locale.js]
[test_moved_extension_metadata.js]
skip-if = true
@ -109,6 +110,7 @@ skip-if = require_signing || !allow_legacy_extensions
head = head_addons.js head_sideload.js
skip-if = os == "linux" # Bug 1613268
[test_startup_enable.js]
[test_startup_isPrivileged.js]
[test_startup_scan.js]
head = head_addons.js head_sideload.js
[test_strictcompatibility.js]
@ -164,6 +166,7 @@ skip-if = os == "win" # Bug 1358846
head = head_addons.js head_compat.js
[test_update_ignorecompat.js]
skip-if = true # Bug 676922 Bug 1437697
[test_update_isPrivileged.js]
[test_update_noSystemAddonUpdate.js]
head = head_addons.js head_system_addons.js
[test_update_strictcompat.js]