зеркало из https://github.com/mozilla/gecko-dev.git
Merge autoland to mozilla-central. a=merge
This commit is contained in:
Коммит
684aefab97
|
@ -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]
|
||||
|
|
Загрузка…
Ссылка в новой задаче