feat: implement privileged extension support
Also includes: * feat: improve support for bootstrapped extensions Added support for custom preference pages. * fix: incorrect loading order for bootstrap loader * fix: BootstrapLoader (cherry picked from commit eb40811e464688c7d2fc58a4330272dde1ec7937)
This commit is contained in:
@@ -229,6 +229,12 @@ BrowserGlue.prototype = {
|
|||||||
// nsIObserver implementation
|
// nsIObserver implementation
|
||||||
observe: async function BG_observe(subject, topic, data) {
|
observe: async function BG_observe(subject, topic, data) {
|
||||||
switch (topic) {
|
switch (topic) {
|
||||||
|
case "app-startup":
|
||||||
|
const { BootstrapLoader } = ChromeUtils.import(
|
||||||
|
"resource:///modules/BootstrapLoader.jsm"
|
||||||
|
);
|
||||||
|
AddonManager.addExternalExtensionLoader(BootstrapLoader);
|
||||||
|
break;
|
||||||
case "notifications-open-settings":
|
case "notifications-open-settings":
|
||||||
this._openPreferences("privacy-permissions");
|
this._openPreferences("privacy-permissions");
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -94,6 +94,12 @@
|
|||||||
"scopes": ["addon_parent"],
|
"scopes": ["addon_parent"],
|
||||||
"paths": [["identity"]]
|
"paths": [["identity"]]
|
||||||
},
|
},
|
||||||
|
"legacy": {
|
||||||
|
"url": "chrome://browser/content/parent/ext-legacy.js",
|
||||||
|
"schema": "chrome://browser/content/schemas/legacy.json",
|
||||||
|
"scopes": ["addon_parent"],
|
||||||
|
"manifest": ["legacy"]
|
||||||
|
},
|
||||||
"menusChild": {
|
"menusChild": {
|
||||||
"schema": "chrome://browser/content/schemas/menus_child.json",
|
"schema": "chrome://browser/content/schemas/menus_child.json",
|
||||||
"scopes": ["addon_child", "content_child", "devtools_child"]
|
"scopes": ["addon_child", "content_child", "devtools_child"]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ browser.jar:
|
|||||||
content/browser/parent/ext-devtools-panels.js (parent/ext-devtools-panels.js)
|
content/browser/parent/ext-devtools-panels.js (parent/ext-devtools-panels.js)
|
||||||
content/browser/parent/ext-find.js (parent/ext-find.js)
|
content/browser/parent/ext-find.js (parent/ext-find.js)
|
||||||
content/browser/parent/ext-history.js (parent/ext-history.js)
|
content/browser/parent/ext-history.js (parent/ext-history.js)
|
||||||
|
content/browser/parent/ext-legacy.js (parent/ext-legacy.js)
|
||||||
content/browser/parent/ext-menus.js (parent/ext-menus.js)
|
content/browser/parent/ext-menus.js (parent/ext-menus.js)
|
||||||
content/browser/parent/ext-normandyAddonStudy.js (parent/ext-normandyAddonStudy.js)
|
content/browser/parent/ext-normandyAddonStudy.js (parent/ext-normandyAddonStudy.js)
|
||||||
content/browser/parent/ext-omnibox.js (parent/ext-omnibox.js)
|
content/browser/parent/ext-omnibox.js (parent/ext-omnibox.js)
|
||||||
|
|||||||
469
browser/components/extensions/parent/ext-legacy.js
Normal file
469
browser/components/extensions/parent/ext-legacy.js
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
ChromeUtils.defineModuleGetter(
|
||||||
|
this,
|
||||||
|
"ChromeManifest",
|
||||||
|
"resource:///modules/ChromeManifest.jsm"
|
||||||
|
);
|
||||||
|
ChromeUtils.defineModuleGetter(
|
||||||
|
this,
|
||||||
|
"ExtensionSupport",
|
||||||
|
"resource:///modules/ExtensionSupport.jsm"
|
||||||
|
);
|
||||||
|
ChromeUtils.defineModuleGetter(
|
||||||
|
this,
|
||||||
|
"Overlays",
|
||||||
|
"resource:///modules/Overlays.jsm"
|
||||||
|
);
|
||||||
|
ChromeUtils.defineModuleGetter(
|
||||||
|
this,
|
||||||
|
"XPIInternal",
|
||||||
|
"resource://gre/modules/addons/XPIProvider.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
Cu.importGlobalProperties(["fetch"]);
|
||||||
|
|
||||||
|
var { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
|
);
|
||||||
|
var { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "BOOTSTRAP_REASONS", () => {
|
||||||
|
const { XPIProvider } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/addons/XPIProvider.jsm"
|
||||||
|
);
|
||||||
|
return XPIProvider.BOOTSTRAP_REASONS;
|
||||||
|
});
|
||||||
|
|
||||||
|
const { Log } = ChromeUtils.import("resource://gre/modules/Log.jsm");
|
||||||
|
var logger = Log.repository.getLogger("addons.bootstrap");
|
||||||
|
|
||||||
|
let bootstrapScopes = new Map();
|
||||||
|
let cachedParams = new Map();
|
||||||
|
|
||||||
|
Services.obs.addObserver(() => {
|
||||||
|
for (let [id, scope] of bootstrapScopes.entries()) {
|
||||||
|
if (ExtensionSupport.loadedBootstrapExtensions.has(id)) {
|
||||||
|
scope.shutdown(
|
||||||
|
{ ...cachedParams.get(id) },
|
||||||
|
BOOTSTRAP_REASONS.APP_SHUTDOWN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "quit-application-granted");
|
||||||
|
|
||||||
|
var { ExtensionError } = ExtensionUtils;
|
||||||
|
|
||||||
|
this.legacy = class extends ExtensionAPI {
|
||||||
|
async onManifestEntry(entryName) {
|
||||||
|
if (this.extension.manifest.legacy) {
|
||||||
|
if (this.extension.manifest.legacy.type == "bootstrap") {
|
||||||
|
await this.registerBootstrapped();
|
||||||
|
} else {
|
||||||
|
await this.registerNonBootstrapped();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is for non-bootstrapped add-ons.
|
||||||
|
|
||||||
|
async registerNonBootstrapped() {
|
||||||
|
this.extension.legacyLoaded = true;
|
||||||
|
|
||||||
|
let state = {
|
||||||
|
id: this.extension.id,
|
||||||
|
pendingOperation: null,
|
||||||
|
version: this.extension.version,
|
||||||
|
};
|
||||||
|
if (ExtensionSupport.loadedLegacyExtensions.has(this.extension.id)) {
|
||||||
|
state = ExtensionSupport.loadedLegacyExtensions.get(this.extension.id);
|
||||||
|
let versionComparison = Services.vc.compare(
|
||||||
|
this.extension.version,
|
||||||
|
state.version
|
||||||
|
);
|
||||||
|
if (versionComparison != 0) {
|
||||||
|
if (versionComparison > 0) {
|
||||||
|
state.pendingOperation = "upgrade";
|
||||||
|
ExtensionSupport.loadedLegacyExtensions.notifyObservers(state);
|
||||||
|
} else if (versionComparison < 0) {
|
||||||
|
state.pendingOperation = "downgrade";
|
||||||
|
ExtensionSupport.loadedLegacyExtensions.notifyObservers(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forget any cached files we might've had from another version of this extension.
|
||||||
|
Services.obs.notifyObservers(null, "startupcache-invalidate");
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
`Legacy WebExtension ${
|
||||||
|
this.extension.id
|
||||||
|
} has already been loaded in this run, refusing to do so again. Please restart.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtensionSupport.loadedLegacyExtensions.set(this.extension.id, state);
|
||||||
|
if (this.extension.startupReason == "ADDON_INSTALL") {
|
||||||
|
// Usually, sideloaded extensions are disabled when they first appear,
|
||||||
|
// but to run calendar tests, we disable this.
|
||||||
|
let scope = XPIInternal.XPIStates.findAddon(this.extension.id).location
|
||||||
|
.scope;
|
||||||
|
let autoDisableScopes = Services.prefs.getIntPref(
|
||||||
|
"extensions.autoDisableScopes"
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the extension was just installed from the distribution folder,
|
||||||
|
// it's in the profile extensions folder. We don't want to disable it.
|
||||||
|
let isDistroAddon = Services.prefs.getBoolPref(
|
||||||
|
"extensions.installedDistroAddon." + this.extension.id,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDistroAddon && scope & autoDisableScopes) {
|
||||||
|
state.pendingOperation = "install";
|
||||||
|
console.log(
|
||||||
|
`Legacy WebExtension ${
|
||||||
|
this.extension.id
|
||||||
|
} loading for other reason than startup (${
|
||||||
|
this.extension.startupReason
|
||||||
|
}), refusing to load immediately.`
|
||||||
|
);
|
||||||
|
ExtensionSupport.loadedLegacyExtensions.notifyObservers(state);
|
||||||
|
|
||||||
|
// Forget any cached files we might've had if this extension was previously installed.
|
||||||
|
Services.obs.notifyObservers(null, "startupcache-invalidate");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.extension.startupReason == "ADDON_ENABLE") {
|
||||||
|
state.pendingOperation = "enable";
|
||||||
|
console.log(
|
||||||
|
`Legacy WebExtension ${
|
||||||
|
this.extension.id
|
||||||
|
} loading for other reason than startup (${
|
||||||
|
this.extension.startupReason
|
||||||
|
}), refusing to load immediately.`
|
||||||
|
);
|
||||||
|
ExtensionSupport.loadedLegacyExtensions.notifyObservers(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let extensionRoot;
|
||||||
|
if (this.extension.rootURI instanceof Ci.nsIJARURI) {
|
||||||
|
extensionRoot = this.extension.rootURI.JARFile.QueryInterface(
|
||||||
|
Ci.nsIFileURL
|
||||||
|
).file;
|
||||||
|
console.log("Loading packed extension from", extensionRoot.path);
|
||||||
|
} else {
|
||||||
|
extensionRoot = this.extension.rootURI.QueryInterface(Ci.nsIFileURL).file;
|
||||||
|
console.log("Loading unpacked extension from", extensionRoot.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have Gecko do as much loading as is still possible
|
||||||
|
try {
|
||||||
|
Cc["@mozilla.org/component-manager-extra;1"]
|
||||||
|
.getService(Ci.nsIComponentManagerExtra)
|
||||||
|
.addLegacyExtensionManifestLocation(extensionRoot);
|
||||||
|
} catch (e) {
|
||||||
|
throw new ExtensionError(e.message, e.fileName, e.lineNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load chrome.manifest
|
||||||
|
let appinfo = Services.appinfo;
|
||||||
|
let options = {
|
||||||
|
application: appinfo.ID,
|
||||||
|
appversion: appinfo.version,
|
||||||
|
platformversion: appinfo.platformVersion,
|
||||||
|
os: appinfo.OS,
|
||||||
|
osversion: Services.sysinfo.getProperty("version"),
|
||||||
|
abi: appinfo.XPCOMABI,
|
||||||
|
};
|
||||||
|
let loader = async filename => {
|
||||||
|
let url = this.extension.getURL(filename);
|
||||||
|
return fetch(url).then(response => response.text());
|
||||||
|
};
|
||||||
|
let chromeManifest = new ChromeManifest(loader, options);
|
||||||
|
await chromeManifest.parse("chrome.manifest");
|
||||||
|
|
||||||
|
// Load preference files
|
||||||
|
console.log("Loading add-on preferences from ", extensionRoot.path);
|
||||||
|
ExtensionSupport.loadAddonPrefs(extensionRoot);
|
||||||
|
|
||||||
|
// Fire profile-after-change notifications, because we are past that event by now
|
||||||
|
console.log("Firing profile-after-change listeners for", this.extension.id);
|
||||||
|
let profileAfterChange = chromeManifest.category.get(
|
||||||
|
"profile-after-change"
|
||||||
|
);
|
||||||
|
for (let contractid of profileAfterChange.values()) {
|
||||||
|
let service = contractid.startsWith("service,");
|
||||||
|
let instance;
|
||||||
|
try {
|
||||||
|
if (service) {
|
||||||
|
instance = Cc[contractid.substr(8)].getService(Ci.nsIObserver);
|
||||||
|
} else {
|
||||||
|
instance = Cc[contractid].createInstance(Ci.nsIObserver);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.observe(null, "profile-after-change", null);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(
|
||||||
|
"Error firing profile-after-change listener for",
|
||||||
|
contractid
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overlays.load must only be called once per window per extension.
|
||||||
|
// We use this WeakSet to remember all windows we've already seen.
|
||||||
|
let seenDocuments = new WeakSet();
|
||||||
|
|
||||||
|
// Listen for new windows to overlay.
|
||||||
|
let documentObserver = {
|
||||||
|
observe(doc) {
|
||||||
|
if (
|
||||||
|
ExtensionCommon.instanceOf(doc, "HTMLDocument") &&
|
||||||
|
!seenDocuments.has(doc)
|
||||||
|
) {
|
||||||
|
seenDocuments.add(doc);
|
||||||
|
Overlays.load(chromeManifest, doc.defaultView);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Services.obs.addObserver(documentObserver, "chrome-document-interactive");
|
||||||
|
|
||||||
|
// Add overlays to all existing windows.
|
||||||
|
getAllWindows().forEach(win => {
|
||||||
|
if (
|
||||||
|
["interactive", "complete"].includes(win.document.readyState) &&
|
||||||
|
!seenDocuments.has(win.document)
|
||||||
|
) {
|
||||||
|
seenDocuments.add(win.document);
|
||||||
|
Overlays.load(chromeManifest, win);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.extension.callOnClose({
|
||||||
|
close: () => {
|
||||||
|
Services.obs.removeObserver(
|
||||||
|
documentObserver,
|
||||||
|
"chrome-document-interactive"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following functions are for bootstrapped add-ons.
|
||||||
|
|
||||||
|
async registerBootstrapped() {
|
||||||
|
let oldParams = cachedParams.get(this.extension.id);
|
||||||
|
let params = {
|
||||||
|
id: this.extension.id,
|
||||||
|
version: this.extension.version,
|
||||||
|
resourceURI: Services.io.newURI(this.extension.resourceURL),
|
||||||
|
installPath: this.extensionFile.path,
|
||||||
|
};
|
||||||
|
cachedParams.set(this.extension.id, { ...params });
|
||||||
|
|
||||||
|
if (
|
||||||
|
oldParams &&
|
||||||
|
["ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(
|
||||||
|
this.extension.startupReason
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
params.oldVersion = oldParams.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
let scope = await this.loadScope();
|
||||||
|
bootstrapScopes.set(this.extension.id, scope);
|
||||||
|
|
||||||
|
if (
|
||||||
|
["ADDON_INSTALL", "ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(
|
||||||
|
this.extension.startupReason
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
scope.install(params, BOOTSTRAP_REASONS[this.extension.startupReason]);
|
||||||
|
}
|
||||||
|
scope.startup(params, BOOTSTRAP_REASONS[this.extension.startupReason]);
|
||||||
|
ExtensionSupport.loadedBootstrapExtensions.add(this.extension.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static onDisable(id) {
|
||||||
|
if (bootstrapScopes.has(id)) {
|
||||||
|
bootstrapScopes
|
||||||
|
.get(id)
|
||||||
|
.shutdown({ ...cachedParams.get(id) }, BOOTSTRAP_REASONS.ADDON_DISABLE);
|
||||||
|
ExtensionSupport.loadedBootstrapExtensions.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static onUpdate(id, manifest) {
|
||||||
|
if (bootstrapScopes.has(id)) {
|
||||||
|
let params = {
|
||||||
|
...cachedParams.get(id),
|
||||||
|
newVersion: manifest.version,
|
||||||
|
};
|
||||||
|
let reason = BOOTSTRAP_REASONS.ADDON_UPGRADE;
|
||||||
|
if (Services.vc.compare(params.newVersion, params.version) < 0) {
|
||||||
|
reason = BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
|
||||||
|
}
|
||||||
|
|
||||||
|
let scope = bootstrapScopes.get(id);
|
||||||
|
scope.shutdown(params, reason);
|
||||||
|
scope.uninstall(params, reason);
|
||||||
|
ExtensionSupport.loadedBootstrapExtensions.delete(id);
|
||||||
|
bootstrapScopes.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static onUninstall(id) {
|
||||||
|
if (bootstrapScopes.has(id)) {
|
||||||
|
bootstrapScopes
|
||||||
|
.get(id)
|
||||||
|
.uninstall(
|
||||||
|
{ ...cachedParams.get(id) },
|
||||||
|
BOOTSTRAP_REASONS.ADDON_UNINSTALL
|
||||||
|
);
|
||||||
|
bootstrapScopes.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get extensionFile() {
|
||||||
|
let uri = Services.io.newURI(this.extension.resourceURL);
|
||||||
|
if (uri instanceof Ci.nsIJARURI) {
|
||||||
|
uri = uri.QueryInterface(Ci.nsIJARURI).JARFile;
|
||||||
|
}
|
||||||
|
return uri.QueryInterface(Ci.nsIFileURL).file;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadScope() {
|
||||||
|
let { extension } = this;
|
||||||
|
let file = this.extensionFile;
|
||||||
|
let uri = this.extension.getURL("bootstrap.js");
|
||||||
|
let principal = Services.scriptSecurityManager.getSystemPrincipal();
|
||||||
|
|
||||||
|
let sandbox = new Cu.Sandbox(principal, {
|
||||||
|
sandboxName: uri,
|
||||||
|
addonId: this.extension.id,
|
||||||
|
wantGlobalProperties: ["ChromeUtils"],
|
||||||
|
metadata: { addonID: this.extension.id, URI: uri },
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object.assign(sandbox, BOOTSTRAP_REASONS);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(
|
||||||
|
sandbox,
|
||||||
|
"console",
|
||||||
|
() => new ConsoleAPI({ consoleID: `addon/${this.extension.id}` })
|
||||||
|
);
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(uri, sandbox);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Error loading bootstrap.js for ${this.extension.id}`, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMethod(name) {
|
||||||
|
if (sandbox.name) {
|
||||||
|
return sandbox.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let method = Cu.evalInSandbox(name, sandbox);
|
||||||
|
return method;
|
||||||
|
} catch (err) {}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
logger.warn(
|
||||||
|
`Add-on ${extension.id} is missing bootstrap method ${name}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let install = findMethod("install");
|
||||||
|
let uninstall = findMethod("uninstall");
|
||||||
|
let startup = findMethod("startup");
|
||||||
|
let shutdown = findMethod("shutdown");
|
||||||
|
|
||||||
|
return {
|
||||||
|
install(...args) {
|
||||||
|
try {
|
||||||
|
install(...args);
|
||||||
|
} catch (ex) {
|
||||||
|
logger.warn(
|
||||||
|
`Exception running bootstrap method install on ${extension.id}`,
|
||||||
|
ex
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uninstall(...args) {
|
||||||
|
try {
|
||||||
|
uninstall(...args);
|
||||||
|
} catch (ex) {
|
||||||
|
logger.warn(
|
||||||
|
`Exception running bootstrap method uninstall on ${extension.id}`,
|
||||||
|
ex
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
// Forget any cached files we might've had from this extension.
|
||||||
|
Services.obs.notifyObservers(null, "startupcache-invalidate");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startup(...args) {
|
||||||
|
logger.debug(`Registering manifest for ${file.path}\n`);
|
||||||
|
Components.manager.addBootstrappedManifestLocation(file);
|
||||||
|
try {
|
||||||
|
startup(...args);
|
||||||
|
} catch (ex) {
|
||||||
|
logger.warn(
|
||||||
|
`Exception running bootstrap method startup on ${extension.id}`,
|
||||||
|
ex
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
shutdown(data, reason) {
|
||||||
|
try {
|
||||||
|
shutdown(data, reason);
|
||||||
|
} catch (ex) {
|
||||||
|
logger.warn(
|
||||||
|
`Exception running bootstrap method shutdown on ${extension.id}`,
|
||||||
|
ex
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (reason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
|
||||||
|
logger.debug(`Removing manifest for ${file.path}\n`);
|
||||||
|
Components.manager.removeBootstrappedManifestLocation(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getAllWindows() {
|
||||||
|
function getChildDocShells(parentDocShell) {
|
||||||
|
let docShells = parentDocShell.getAllDocShellsInSubtree(
|
||||||
|
Ci.nsIDocShellTreeItem.typeAll,
|
||||||
|
Ci.nsIDocShell.ENUMERATE_FORWARDS
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let docShell of docShells) {
|
||||||
|
docShell
|
||||||
|
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIWebProgress);
|
||||||
|
domWindows.push(docShell.domWindow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let domWindows = [];
|
||||||
|
for (let win of Services.ww.getWindowEnumerator()) {
|
||||||
|
let parentDocShell = win
|
||||||
|
.getInterface(Ci.nsIWebNavigation)
|
||||||
|
.QueryInterface(Ci.nsIDocShell);
|
||||||
|
getChildDocShells(parentDocShell);
|
||||||
|
}
|
||||||
|
return domWindows;
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ browser.jar:
|
|||||||
content/browser/schemas/devtools_panels.json
|
content/browser/schemas/devtools_panels.json
|
||||||
content/browser/schemas/find.json
|
content/browser/schemas/find.json
|
||||||
content/browser/schemas/history.json
|
content/browser/schemas/history.json
|
||||||
|
content/browser/schemas/legacy.json
|
||||||
content/browser/schemas/menus.json
|
content/browser/schemas/menus.json
|
||||||
content/browser/schemas/menus_child.json
|
content/browser/schemas/menus_child.json
|
||||||
content/browser/schemas/normandyAddonStudy.json
|
content/browser/schemas/normandyAddonStudy.json
|
||||||
|
|||||||
43
browser/components/extensions/schemas/legacy.json
Normal file
43
browser/components/extensions/schemas/legacy.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"namespace": "manifest",
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"$extend": "WebExtensionManifest",
|
||||||
|
"properties": {
|
||||||
|
"legacy": {
|
||||||
|
"optional": true,
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["xul", "bootstrap"],
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"page": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"open_in_tab": {
|
||||||
|
"type": "boolean",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -4171,7 +4171,9 @@ export var AddonManager = {
|
|||||||
AUTOUPDATE_DEFAULT: 1,
|
AUTOUPDATE_DEFAULT: 1,
|
||||||
// Indicates that the Addon should update automatically.
|
// Indicates that the Addon should update automatically.
|
||||||
AUTOUPDATE_ENABLE: 2,
|
AUTOUPDATE_ENABLE: 2,
|
||||||
|
// Constants for how Addon options should be shown.
|
||||||
|
// Options will be opened in a new window
|
||||||
|
OPTIONS_TYPE_DIALOG: 1,
|
||||||
// Constants for how Addon options should be shown.
|
// Constants for how Addon options should be shown.
|
||||||
// Options will be displayed in a new tab, if possible
|
// Options will be displayed in a new tab, if possible
|
||||||
OPTIONS_TYPE_TAB: 3,
|
OPTIONS_TYPE_TAB: 3,
|
||||||
|
|||||||
@@ -990,6 +990,7 @@ export class AddonWrapper {
|
|||||||
|
|
||||||
if (addon.optionsType) {
|
if (addon.optionsType) {
|
||||||
switch (parseInt(addon.optionsType, 10)) {
|
switch (parseInt(addon.optionsType, 10)) {
|
||||||
|
case lazy.AddonManager.OPTIONS_TYPE_DIALOG:
|
||||||
case lazy.AddonManager.OPTIONS_TYPE_TAB:
|
case lazy.AddonManager.OPTIONS_TYPE_TAB:
|
||||||
case lazy.AddonManager.OPTIONS_TYPE_INLINE_BROWSER:
|
case lazy.AddonManager.OPTIONS_TYPE_INLINE_BROWSER:
|
||||||
return hasOptionsURL ? addon.optionsType : null;
|
return hasOptionsURL ? addon.optionsType : null;
|
||||||
@@ -2717,8 +2718,8 @@ export const XPIDatabase = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.isDisabledLegacy(aAddon)) {
|
if (this.isDisabledLegacy(aAddon)) {
|
||||||
logger.warn(`disabling legacy extension ${aAddon.id}`);
|
logger.warn(`enabling legacy extension ${aAddon.id}`);
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lazy.AddonManager.checkCompatibility) {
|
if (lazy.AddonManager.checkCompatibility) {
|
||||||
|
|||||||
458
waterfox/browser/extensions/common/BootstrapLoader.jsm
Normal file
458
waterfox/browser/extensions/common/BootstrapLoader.jsm
Normal file
@@ -0,0 +1,458 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["BootstrapLoader"];
|
||||||
|
|
||||||
|
const { AddonManager } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/AddonManager.jsm"
|
||||||
|
);
|
||||||
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
|
AddonInternal: "resource://gre/modules/addons/XPIDatabase.jsm",
|
||||||
|
Blocklist: "resource://gre/modules/Blocklist.jsm",
|
||||||
|
ConsoleAPI: "resource://gre/modules/Console.jsm",
|
||||||
|
InstallRDF: "resource:///modules/RDFManifestConverter.jsm",
|
||||||
|
Services: "resource://gre/modules/Services.jsm",
|
||||||
|
});
|
||||||
|
|
||||||
|
Services.obs.addObserver(doc => {
|
||||||
|
if (
|
||||||
|
doc.location.protocol + doc.location.pathname === "about:addons" ||
|
||||||
|
doc.location.protocol + doc.location.pathname ===
|
||||||
|
"chrome://mozapps/content/extensions/aboutaddons.html"
|
||||||
|
) {
|
||||||
|
const win = doc.defaultView;
|
||||||
|
let handleEvent_orig = win.customElements.get("addon-card").prototype
|
||||||
|
.handleEvent;
|
||||||
|
win.customElements.get("addon-card").prototype.handleEvent = function(e) {
|
||||||
|
if (
|
||||||
|
e.type === "click" &&
|
||||||
|
e.target.getAttribute("action") === "preferences" &&
|
||||||
|
this.addon.optionsType == AddonManager.OPTIONS_TYPE_DIALOG
|
||||||
|
) {
|
||||||
|
let windows = Services.wm.getEnumerator(null);
|
||||||
|
while (windows.hasMoreElements()) {
|
||||||
|
let win2 = windows.getNext();
|
||||||
|
if (win2.closed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (win2.document.documentURI == this.addon.optionsURL) {
|
||||||
|
win2.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let features = "chrome,titlebar,toolbar,centerscreen";
|
||||||
|
let instantApply = Services.prefs.getBoolPref(
|
||||||
|
"browser.preferences.instantApply"
|
||||||
|
);
|
||||||
|
features += instantApply ? ",dialog=no" : "";
|
||||||
|
win.docShell.rootTreeItem.domWindow.openDialog(
|
||||||
|
this.addon.optionsURL,
|
||||||
|
this.addon.id,
|
||||||
|
features
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
handleEvent_orig.apply(this, arguments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let update_orig = win.customElements.get("addon-options").prototype.update;
|
||||||
|
win.customElements.get("addon-options").prototype.update = function(
|
||||||
|
card,
|
||||||
|
addon
|
||||||
|
) {
|
||||||
|
update_orig.apply(this, arguments);
|
||||||
|
if (addon.optionsType == AddonManager.OPTIONS_TYPE_DIALOG) {
|
||||||
|
this.querySelector('panel-item[action="preferences"]').hidden = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, "chrome-document-loaded");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "BOOTSTRAP_REASONS", () => {
|
||||||
|
const { XPIProvider } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/addons/XPIProvider.jsm"
|
||||||
|
);
|
||||||
|
return XPIProvider.BOOTSTRAP_REASONS;
|
||||||
|
});
|
||||||
|
|
||||||
|
const { Log } = ChromeUtils.import("resource://gre/modules/Log.jsm");
|
||||||
|
var logger = Log.repository.getLogger("addons.bootstrap");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid IDs fit this pattern.
|
||||||
|
*/
|
||||||
|
var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
|
||||||
|
|
||||||
|
// Properties that exist in the install manifest
|
||||||
|
const PROP_METADATA = [
|
||||||
|
"id",
|
||||||
|
"version",
|
||||||
|
"type",
|
||||||
|
"internalName",
|
||||||
|
"updateURL",
|
||||||
|
"optionsURL",
|
||||||
|
"optionsType",
|
||||||
|
"aboutURL",
|
||||||
|
"iconURL",
|
||||||
|
];
|
||||||
|
const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
|
||||||
|
const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"];
|
||||||
|
|
||||||
|
// Map new string type identifiers to old style nsIUpdateItem types.
|
||||||
|
// Retired values:
|
||||||
|
// 32 = multipackage xpi file
|
||||||
|
// 8 = locale
|
||||||
|
// 256 = apiextension
|
||||||
|
// 128 = experiment
|
||||||
|
// theme = 4
|
||||||
|
const TYPES = {
|
||||||
|
extension: 2,
|
||||||
|
dictionary: 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
const COMPATIBLE_BY_DEFAULT_TYPES = {
|
||||||
|
extension: true,
|
||||||
|
dictionary: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
|
||||||
|
|
||||||
|
function isXPI(filename) {
|
||||||
|
let ext = filename.slice(-4).toLowerCase();
|
||||||
|
return ext === ".xpi" || ext === ".zip";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a jar: URI for a file inside a ZIP file.
|
||||||
|
*
|
||||||
|
* @param {nsIFile} aJarfile
|
||||||
|
* The ZIP file as an nsIFile
|
||||||
|
* @param {string} aPath
|
||||||
|
* The path inside the ZIP file
|
||||||
|
* @returns {nsIURI}
|
||||||
|
* An nsIURI for the file
|
||||||
|
*/
|
||||||
|
function buildJarURI(aJarfile, aPath) {
|
||||||
|
let uri = Services.io.newFileURI(aJarfile);
|
||||||
|
uri = "jar:" + uri.spec + "!/" + aPath;
|
||||||
|
return Services.io.newURI(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an nsIURI for a file within another file, either a directory or an XPI
|
||||||
|
* file. If aFile is a directory then this will return a file: URI, if it is an
|
||||||
|
* XPI file then it will return a jar: URI.
|
||||||
|
*
|
||||||
|
* @param {nsIFile} aFile
|
||||||
|
* The file containing the resources, must be either a directory or an
|
||||||
|
* XPI file
|
||||||
|
* @param {string} aPath
|
||||||
|
* The path to find the resource at, "/" separated. If aPath is empty
|
||||||
|
* then the uri to the root of the contained files will be returned
|
||||||
|
* @returns {nsIURI}
|
||||||
|
* An nsIURI pointing at the resource
|
||||||
|
*/
|
||||||
|
function getURIForResourceInFile(aFile, aPath) {
|
||||||
|
if (!isXPI(aFile.leafName)) {
|
||||||
|
let resource = aFile.clone();
|
||||||
|
if (aPath) {
|
||||||
|
aPath.split("/").forEach(part => resource.append(part));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Services.io.newFileURI(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildJarURI(aFile, aPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
var BootstrapLoader = {
|
||||||
|
name: "bootstrap",
|
||||||
|
manifestFile: "install.rdf",
|
||||||
|
async loadManifest(pkg) {
|
||||||
|
/**
|
||||||
|
* Reads locale properties from either the main install manifest root or
|
||||||
|
* an em:localized section in the install manifest.
|
||||||
|
*
|
||||||
|
* @param {Object} aSource
|
||||||
|
* The resource to read the properties from.
|
||||||
|
* @param {boolean} isDefault
|
||||||
|
* True if the locale is to be read from the main install manifest
|
||||||
|
* root
|
||||||
|
* @param {string[]} aSeenLocales
|
||||||
|
* An array of locale names already seen for this install manifest.
|
||||||
|
* Any locale names seen as a part of this function will be added to
|
||||||
|
* this array
|
||||||
|
* @returns {Object}
|
||||||
|
* an object containing the locale properties
|
||||||
|
*/
|
||||||
|
function readLocale(aSource, isDefault, aSeenLocales) {
|
||||||
|
let locale = {};
|
||||||
|
if (!isDefault) {
|
||||||
|
locale.locales = [];
|
||||||
|
for (let localeName of aSource.locales || []) {
|
||||||
|
if (!localeName) {
|
||||||
|
logger.warn("Ignoring empty locale in localized properties");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (aSeenLocales.includes(localeName)) {
|
||||||
|
logger.warn("Ignoring duplicate locale in localized properties");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
aSeenLocales.push(localeName);
|
||||||
|
locale.locales.push(localeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!locale.locales.length) {
|
||||||
|
logger.warn("Ignoring localized properties with no listed locales");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let prop of [...PROP_LOCALE_SINGLE, ...PROP_LOCALE_MULTI]) {
|
||||||
|
if (hasOwnProperty(aSource, prop)) {
|
||||||
|
locale[prop] = aSource[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
let manifestData = await pkg.readString("install.rdf");
|
||||||
|
let manifest = InstallRDF.loadFromString(manifestData).decode();
|
||||||
|
|
||||||
|
let addon = new AddonInternal();
|
||||||
|
for (let prop of PROP_METADATA) {
|
||||||
|
if (hasOwnProperty(manifest, prop)) {
|
||||||
|
addon[prop] = manifest[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!addon.type) {
|
||||||
|
addon.type = "extension";
|
||||||
|
} else {
|
||||||
|
let type = addon.type;
|
||||||
|
addon.type = null;
|
||||||
|
for (let name in TYPES) {
|
||||||
|
if (TYPES[name] == type) {
|
||||||
|
addon.type = name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(addon.type in TYPES)) {
|
||||||
|
throw new Error("Install manifest specifies unknown type: " + addon.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!addon.id) {
|
||||||
|
throw new Error("No ID in install manifest");
|
||||||
|
}
|
||||||
|
if (!gIDTest.test(addon.id)) {
|
||||||
|
throw new Error("Illegal add-on ID " + addon.id);
|
||||||
|
}
|
||||||
|
if (!addon.version) {
|
||||||
|
throw new Error("No version in install manifest");
|
||||||
|
}
|
||||||
|
|
||||||
|
addon.strictCompatibility =
|
||||||
|
!(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||
|
||||||
|
manifest.strictCompatibility == "true";
|
||||||
|
|
||||||
|
// Only read these properties for extensions.
|
||||||
|
if (addon.type == "extension") {
|
||||||
|
if (manifest.bootstrap != "true") {
|
||||||
|
throw new Error("Non-restartless extensions no longer supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
addon.optionsType &&
|
||||||
|
addon.optionsType != AddonManager.OPTIONS_TYPE_DIALOG &&
|
||||||
|
addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE_BROWSER &&
|
||||||
|
addon.optionsType != AddonManager.OPTIONS_TYPE_TAB
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Install manifest specifies unknown optionsType: " + addon.optionsType
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Convert legacy dictionaries into a format the WebExtension
|
||||||
|
// dictionary loader can process.
|
||||||
|
if (addon.type === "dictionary") {
|
||||||
|
addon.loader = null;
|
||||||
|
let dictionaries = {};
|
||||||
|
await pkg.iterFiles(({ path }) => {
|
||||||
|
let match = /^dictionaries\/([^\/]+)\.dic$/.exec(path);
|
||||||
|
if (match) {
|
||||||
|
let lang = match[1].replace(/_/g, "-");
|
||||||
|
dictionaries[lang] = match[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addon.startupData = { dictionaries };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only extensions are allowed to provide an optionsURL, optionsType,
|
||||||
|
// optionsBrowserStyle, or aboutURL. For all other types they are silently ignored
|
||||||
|
addon.aboutURL = null;
|
||||||
|
addon.optionsBrowserStyle = null;
|
||||||
|
addon.optionsType = null;
|
||||||
|
addon.optionsURL = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
addon.defaultLocale = readLocale(manifest, true);
|
||||||
|
|
||||||
|
let seenLocales = [];
|
||||||
|
addon.locales = [];
|
||||||
|
for (let localeData of manifest.localized || []) {
|
||||||
|
let locale = readLocale(localeData, false, seenLocales);
|
||||||
|
if (locale) {
|
||||||
|
addon.locales.push(locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dependencies = new Set(manifest.dependencies);
|
||||||
|
addon.dependencies = Object.freeze(Array.from(dependencies));
|
||||||
|
|
||||||
|
let seenApplications = [];
|
||||||
|
addon.targetApplications = [];
|
||||||
|
for (let targetApp of manifest.targetApplications || []) {
|
||||||
|
if (!targetApp.id || !targetApp.minVersion || !targetApp.maxVersion) {
|
||||||
|
logger.warn(
|
||||||
|
"Ignoring invalid targetApplication entry in install manifest"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (seenApplications.includes(targetApp.id)) {
|
||||||
|
logger.warn(
|
||||||
|
"Ignoring duplicate targetApplication entry for " +
|
||||||
|
targetApp.id +
|
||||||
|
" in install manifest"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seenApplications.push(targetApp.id);
|
||||||
|
addon.targetApplications.push(targetApp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that we don't need to check for duplicate targetPlatform entries since
|
||||||
|
// the RDF service coalesces them for us.
|
||||||
|
addon.targetPlatforms = [];
|
||||||
|
for (let targetPlatform of manifest.targetPlatforms || []) {
|
||||||
|
let platform = {
|
||||||
|
os: null,
|
||||||
|
abi: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
let pos = targetPlatform.indexOf("_");
|
||||||
|
if (pos != -1) {
|
||||||
|
platform.os = targetPlatform.substring(0, pos);
|
||||||
|
platform.abi = targetPlatform.substring(pos + 1);
|
||||||
|
} else {
|
||||||
|
platform.os = targetPlatform;
|
||||||
|
}
|
||||||
|
|
||||||
|
addon.targetPlatforms.push(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
addon.userDisabled = false;
|
||||||
|
addon.softDisabled = addon.blocklistState == Blocklist.STATE_SOFTBLOCKED;
|
||||||
|
addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
|
||||||
|
|
||||||
|
addon.userPermissions = null;
|
||||||
|
|
||||||
|
addon.icons = {};
|
||||||
|
if (await pkg.hasResource("icon.png")) {
|
||||||
|
addon.icons[32] = "icon.png";
|
||||||
|
addon.icons[48] = "icon.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await pkg.hasResource("icon64.png")) {
|
||||||
|
addon.icons[64] = "icon64.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
return addon;
|
||||||
|
},
|
||||||
|
|
||||||
|
loadScope(addon) {
|
||||||
|
let file = addon.file || addon._sourceBundle;
|
||||||
|
let uri = getURIForResourceInFile(file, "bootstrap.js").spec;
|
||||||
|
let principal = Services.scriptSecurityManager.getSystemPrincipal();
|
||||||
|
|
||||||
|
let sandbox = new Cu.Sandbox(principal, {
|
||||||
|
sandboxName: uri,
|
||||||
|
addonId: addon.id,
|
||||||
|
wantGlobalProperties: ["ChromeUtils"],
|
||||||
|
metadata: { addonID: addon.id, URI: uri },
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object.assign(sandbox, BOOTSTRAP_REASONS);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(
|
||||||
|
sandbox,
|
||||||
|
"console",
|
||||||
|
() => new ConsoleAPI({ consoleID: `addon/${addon.id}` })
|
||||||
|
);
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(uri, sandbox);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Error loading bootstrap.js for ${addon.id}`, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMethod(name) {
|
||||||
|
if (sandbox.name) {
|
||||||
|
return sandbox.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let method = Cu.evalInSandbox(name, sandbox);
|
||||||
|
return method;
|
||||||
|
} catch (err) {}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
logger.warn(`Add-on ${addon.id} is missing bootstrap method ${name}`);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let install = findMethod("install");
|
||||||
|
let uninstall = findMethod("uninstall");
|
||||||
|
let startup = findMethod("startup");
|
||||||
|
let shutdown = findMethod("shutdown");
|
||||||
|
|
||||||
|
return {
|
||||||
|
install: (...args) => install(...args),
|
||||||
|
|
||||||
|
uninstall(...args) {
|
||||||
|
uninstall(...args);
|
||||||
|
// Forget any cached files we might've had from this extension.
|
||||||
|
Services.obs.notifyObservers(null, "startupcache-invalidate");
|
||||||
|
},
|
||||||
|
|
||||||
|
startup(...args) {
|
||||||
|
if (addon.type == "extension") {
|
||||||
|
logger.debug(`Registering manifest for ${file.path}\n`);
|
||||||
|
Components.manager.addBootstrappedManifestLocation(file);
|
||||||
|
}
|
||||||
|
return startup(...args);
|
||||||
|
},
|
||||||
|
|
||||||
|
shutdown(data, reason) {
|
||||||
|
try {
|
||||||
|
return shutdown(data, reason);
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
if (reason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
|
||||||
|
logger.debug(`Removing manifest for ${file.path}\n`);
|
||||||
|
Components.manager.removeBootstrappedManifestLocation(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
363
waterfox/browser/extensions/common/ChromeManifest.jsm
Normal file
363
waterfox/browser/extensions/common/ChromeManifest.jsm
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
this.EXPORTED_SYMBOLS = ["ChromeManifest"];
|
||||||
|
|
||||||
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parser for chrome.manifest files. Implements a subset of
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Mozilla/Chrome_Registration
|
||||||
|
*/
|
||||||
|
class ChromeManifest {
|
||||||
|
/**
|
||||||
|
* Constructs the chrome.manifest parser
|
||||||
|
*
|
||||||
|
* @param {Function} loader An asynchronous function that will load further files, e.g.
|
||||||
|
* those included via the |manifest| instruction. The
|
||||||
|
* function will take the file as an argument and should
|
||||||
|
* resolve with the string contents of that file
|
||||||
|
* @param {Object} options Object describing the current system. The keys are manifest
|
||||||
|
* instructions
|
||||||
|
*/
|
||||||
|
constructor(loader, options) {
|
||||||
|
this.loader = loader;
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
this.overlay = new DefaultMap(() => []);
|
||||||
|
this.locales = new DefaultMap(() => new Map());
|
||||||
|
this.style = new DefaultMap(() => new Set());
|
||||||
|
this.category = new DefaultMap(() => new Map());
|
||||||
|
|
||||||
|
this.component = new Map();
|
||||||
|
this.contract = new Map();
|
||||||
|
|
||||||
|
this.content = new Map();
|
||||||
|
this.skin = new Map();
|
||||||
|
this.resource = new Map();
|
||||||
|
this.override = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the given file.
|
||||||
|
*
|
||||||
|
* @param {string} filename The filename to load
|
||||||
|
* @param {string} base The relative directory this file is expected to be in.
|
||||||
|
* @returns {Promise} Resolved when loading completes
|
||||||
|
*/
|
||||||
|
async parse(filename = "chrome.manifest", base = "") {
|
||||||
|
await this.parseString(await this.loader(filename), base);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the given string.
|
||||||
|
*
|
||||||
|
* @param {string} data The file data to load
|
||||||
|
* @param {string} base The relative directory this file is expected to be in.
|
||||||
|
* @returns {Promise} Resolved when loading completes
|
||||||
|
*/
|
||||||
|
async parseString(data, base = "") {
|
||||||
|
let lines = data.split("\n");
|
||||||
|
let extraManifests = [];
|
||||||
|
for (let line of lines) {
|
||||||
|
let parts = line.trim().split(/\s+/);
|
||||||
|
let directive = parts.shift();
|
||||||
|
switch (directive) {
|
||||||
|
case "manifest":
|
||||||
|
extraManifests.push(this._parseManifest(base, ...parts));
|
||||||
|
break;
|
||||||
|
case "component":
|
||||||
|
this._parseComponent(...parts);
|
||||||
|
break;
|
||||||
|
case "contract":
|
||||||
|
this._parseContract(...parts);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "category":
|
||||||
|
this._parseCategory(...parts);
|
||||||
|
break;
|
||||||
|
case "content":
|
||||||
|
this._parseContent(...parts);
|
||||||
|
break;
|
||||||
|
case "locale":
|
||||||
|
this._parseLocale(...parts);
|
||||||
|
break;
|
||||||
|
case "skin":
|
||||||
|
this._parseSkin(...parts);
|
||||||
|
break;
|
||||||
|
case "resource":
|
||||||
|
this._parseResource(...parts);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "overlay":
|
||||||
|
this._parseOverlay(...parts);
|
||||||
|
break;
|
||||||
|
case "style":
|
||||||
|
this._parseStyle(...parts);
|
||||||
|
break;
|
||||||
|
case "override":
|
||||||
|
this._parseOverride(...parts);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(extraManifests);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the flags provided for the instruction match our options
|
||||||
|
*
|
||||||
|
* @param {string[]} flags An array of raw flag values in the form key=value.
|
||||||
|
* @returns {boolean} True, if the flags match the options provided in the constructor
|
||||||
|
*/
|
||||||
|
_parseFlags(flags) {
|
||||||
|
if (!flags.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let matchString = (a, sign, b) => {
|
||||||
|
if (sign != "=") {
|
||||||
|
console.warn(
|
||||||
|
`Invalid sign ${sign} in ${a}${sign}${b}, dropping manifest instruction`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return a == b;
|
||||||
|
};
|
||||||
|
|
||||||
|
let matchVersion = (a, sign, b) => {
|
||||||
|
switch (sign) {
|
||||||
|
case "=":
|
||||||
|
return Services.vc.compare(a, b) == 0;
|
||||||
|
case ">":
|
||||||
|
return Services.vc.compare(a, b) > 0;
|
||||||
|
case "<":
|
||||||
|
return Services.vc.compare(a, b) < 0;
|
||||||
|
case ">=":
|
||||||
|
return Services.vc.compare(a, b) >= 0;
|
||||||
|
case "<=":
|
||||||
|
return Services.vc.compare(a, b) <= 0;
|
||||||
|
default:
|
||||||
|
console.warn(
|
||||||
|
`Invalid sign ${sign} in ${a}${sign}${b}, dropping manifest instruction`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let flagMatches = (key, typeMatch) => {
|
||||||
|
return (
|
||||||
|
!flagdata.has(key) ||
|
||||||
|
flagdata.get(key).some(val => typeMatch(this.options[key], ...val))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let flagdata = new DefaultMap(() => []);
|
||||||
|
|
||||||
|
for (let flag of flags) {
|
||||||
|
let match = flag.match(/(\w+)(>=|<=|<|>|=)(.*)/);
|
||||||
|
if (match) {
|
||||||
|
flagdata.get(match[1]).push([match[2], match[3]]);
|
||||||
|
} else {
|
||||||
|
console.warn(`Invalid flag ${flag}, dropping manifest instruction`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
flagMatches("application", matchString) &&
|
||||||
|
flagMatches("appversion", matchVersion) &&
|
||||||
|
flagMatches("platformversion", matchVersion) &&
|
||||||
|
flagMatches("os", matchString) &&
|
||||||
|
flagMatches("osversion", matchVersion) &&
|
||||||
|
flagMatches("abi", matchString)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the manifest instruction, to load other files
|
||||||
|
*
|
||||||
|
* @param {string} base The base directory the manifest file is in
|
||||||
|
* @param {string} filename The file and path to load
|
||||||
|
* @param {...string} flags The flags for this instruction
|
||||||
|
* @returns {Promise} Promise resolved when the manifest is loaded
|
||||||
|
*/
|
||||||
|
async _parseManifest(base, filename, ...flags) {
|
||||||
|
if (this._parseFlags(flags)) {
|
||||||
|
let dirparts = filename.split("/");
|
||||||
|
dirparts.pop();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.parse(filename, base + "/" + dirparts.join("/"));
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Could not read manifest '${base}/${filename}'.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the component instruction, to load xpcom components
|
||||||
|
*
|
||||||
|
* @param {string} classid The xpcom class id to load
|
||||||
|
* @param {string} loction The file location of this component
|
||||||
|
* @param {...string} flags The flags for this instruction
|
||||||
|
*/
|
||||||
|
_parseComponent(classid, location, ...flags) {
|
||||||
|
if (this._parseFlags(flags)) {
|
||||||
|
this.component.set(classid, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the contract instruction, to load xpcom contract ids
|
||||||
|
*
|
||||||
|
* @param {string} contractid The xpcom contract id to load
|
||||||
|
* @param {string} location The file location of this component
|
||||||
|
* @param {...string} flags The flags for this instruction
|
||||||
|
*/
|
||||||
|
_parseContract(contractid, location, ...flags) {
|
||||||
|
if (this._parseFlags(flags)) {
|
||||||
|
this.contract.set(contractid, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the category instruction, to set up xpcom categories
|
||||||
|
*
|
||||||
|
* @param {string} category The name of the category
|
||||||
|
* @param {string} entryName The category entry name
|
||||||
|
* @param {string} value The category entry value
|
||||||
|
* @param {...string} flags The flags for this instruction
|
||||||
|
*/
|
||||||
|
_parseCategory(category, entryName, value, ...flags) {
|
||||||
|
if (this._parseFlags(flags)) {
|
||||||
|
this.category.get(category).set(entryName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the content instruction, to set chrome content locations
|
||||||
|
*
|
||||||
|
* @param {string} shortname The content short name, e.g. chrome://shortname/content/
|
||||||
|
* @param {string} location The location for this content registration
|
||||||
|
* @param {...string} flags The flags for this instruction
|
||||||
|
*/
|
||||||
|
_parseContent(shortname, location, ...flags) {
|
||||||
|
if (this._parseFlags(flags)) {
|
||||||
|
this.content.set(shortname, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the locale instruction, to set chrome locale locations
|
||||||
|
*
|
||||||
|
* @param {string} shortname The locale short name, e.g. chrome://shortname/locale/
|
||||||
|
* @param {string} location The location for this locale registration
|
||||||
|
* @param {...string} flags The flags for this instruction
|
||||||
|
*/
|
||||||
|
_parseLocale(shortname, locale, location, ...flags) {
|
||||||
|
if (this._parseFlags(flags)) {
|
||||||
|
this.locales.get(shortname).set(locale, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the skin instruction, to set chrome skin locations
|
||||||
|
*
|
||||||
|
* @param {string} shortname The skin short name, e.g. chrome://shortname/skin/
|
||||||
|
* @param {string} location The location for this skin registration
|
||||||
|
* @param {...string} flags The flags for this instruction
|
||||||
|
*/
|
||||||
|
_parseSkin(packagename, skinname, location, ...flags) {
|
||||||
|
if (this._parseFlags(flags)) {
|
||||||
|
this.skin.set(packagename, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the resource instruction, to set up resource uri substitutions
|
||||||
|
*
|
||||||
|
* @param {string} packagename The resource package name, e.g. resource://packagename/
|
||||||
|
* @param {string} url The location for this content registration
|
||||||
|
* @param {...string} flags The flags for this instruction
|
||||||
|
*/
|
||||||
|
_parseResource(packagename, location, ...flags) {
|
||||||
|
if (this._parseFlags(flags)) {
|
||||||
|
this.resource.set(packagename, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the overlay instruction, to set up xul overlays
|
||||||
|
*
|
||||||
|
* @param {string} targetUrl The chrome target url
|
||||||
|
* @param {string} overlayUrl The url of the xul overlay
|
||||||
|
* @param {...string} flags The flags for this instruction
|
||||||
|
*/
|
||||||
|
_parseOverlay(targetUrl, overlayUrl, ...flags) {
|
||||||
|
if (this._parseFlags(flags)) {
|
||||||
|
this.overlay.get(targetUrl).push(overlayUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the style instruction, to add stylesheets into chrome windows
|
||||||
|
*
|
||||||
|
* @param {string} uri The uri of the chrome window
|
||||||
|
* @param {string} sheet The uri of the css sheet
|
||||||
|
* @param {...string} flags The flags for this instruction
|
||||||
|
*/
|
||||||
|
_parseStyle(uri, sheet, ...flags) {
|
||||||
|
if (this._parseFlags(flags)) {
|
||||||
|
this.style.get(uri).add(sheet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the override instruction, to set chrome uri overrides
|
||||||
|
*
|
||||||
|
* @param {string} uri The uri being overridden
|
||||||
|
* @param {string} newuri The replacement uri for the original location
|
||||||
|
* @param {...string} flags The flags for this instruction
|
||||||
|
*/
|
||||||
|
_parseOverride(uri, newuri, ...flags) {
|
||||||
|
if (this._parseFlags(flags)) {
|
||||||
|
this.override.set(uri, newuri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A default map, which assumes a default value on get() if the key doesn't exist
|
||||||
|
*/
|
||||||
|
class DefaultMap extends Map {
|
||||||
|
/**
|
||||||
|
* Constructs the default map
|
||||||
|
*
|
||||||
|
* @param {Function} _default A function that returns the default value for this map
|
||||||
|
* @param {*} iterable An iterable to initialize the map with
|
||||||
|
*/
|
||||||
|
constructor(_default, iterable) {
|
||||||
|
super(iterable);
|
||||||
|
this._default = _default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the given key, creating if necessary
|
||||||
|
*
|
||||||
|
* @param {string} key The key of the map to get
|
||||||
|
* @param {boolean} create True, if the key should be created in case it doesn't exist.
|
||||||
|
*/
|
||||||
|
get(key, create = true) {
|
||||||
|
if (this.has(key)) {
|
||||||
|
return super.get(key);
|
||||||
|
} else if (create) {
|
||||||
|
this.set(key, this._default());
|
||||||
|
return super.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._default();
|
||||||
|
}
|
||||||
|
}
|
||||||
410
waterfox/browser/extensions/common/ExtensionSupport.jsm
Normal file
410
waterfox/browser/extensions/common/ExtensionSupport.jsm
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper functions for use by extensions that should ease them plug
|
||||||
|
* into the application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
this.EXPORTED_SYMBOLS = ["ExtensionSupport"];
|
||||||
|
|
||||||
|
const { AddonManager } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/AddonManager.jsm"
|
||||||
|
);
|
||||||
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
// ChromeUtils.import("resource://gre/modules/Deprecated.jsm") - needed for warning.
|
||||||
|
const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
|
||||||
|
|
||||||
|
var { fixIterator } = ChromeUtils.import(
|
||||||
|
"resource:///modules/iteratorUtils.jsm"
|
||||||
|
);
|
||||||
|
const { IOUtils } = ChromeUtils.import("resource:///modules/IOUtils.jsm");
|
||||||
|
|
||||||
|
var extensionHooks = new Map();
|
||||||
|
var legacyExtensions = new Map();
|
||||||
|
var openWindowList;
|
||||||
|
|
||||||
|
var ExtensionSupport = {
|
||||||
|
/**
|
||||||
|
* A Map-like object which tracks legacy extension status. The "has" method
|
||||||
|
* returns only active extensions for compatibility with existing code.
|
||||||
|
*/
|
||||||
|
loadedLegacyExtensions: {
|
||||||
|
set(id, state) {
|
||||||
|
legacyExtensions.set(id, state);
|
||||||
|
},
|
||||||
|
get(id) {
|
||||||
|
return legacyExtensions.get(id);
|
||||||
|
},
|
||||||
|
has(id) {
|
||||||
|
if (!legacyExtensions.has(id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = legacyExtensions.get(id);
|
||||||
|
return !["install", "enable"].includes(state.pendingOperation);
|
||||||
|
},
|
||||||
|
hasAnyState(id) {
|
||||||
|
return legacyExtensions.has(id);
|
||||||
|
},
|
||||||
|
_maybeDelete(id, newPendingOperation) {
|
||||||
|
if (!legacyExtensions.has(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = legacyExtensions.get(id);
|
||||||
|
if (
|
||||||
|
state.pendingOperation == "enable" &&
|
||||||
|
newPendingOperation == "disable"
|
||||||
|
) {
|
||||||
|
legacyExtensions.delete(id);
|
||||||
|
this.notifyObservers(state);
|
||||||
|
} else if (
|
||||||
|
state.pendingOperation == "install" &&
|
||||||
|
newPendingOperation == "uninstall"
|
||||||
|
) {
|
||||||
|
legacyExtensions.delete(id);
|
||||||
|
this.notifyObservers(state);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
notifyObservers(state) {
|
||||||
|
let wrappedState = { wrappedJSObject: state };
|
||||||
|
Services.obs.notifyObservers(wrappedState, "legacy-addon-status-changed");
|
||||||
|
},
|
||||||
|
// AddonListener
|
||||||
|
onDisabled(ev) {
|
||||||
|
this._maybeDelete(ev.id, "disable");
|
||||||
|
},
|
||||||
|
onUninstalled(ev) {
|
||||||
|
this._maybeDelete(ev.id, "uninstall");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
loadAddonPrefs(addonFile) {
|
||||||
|
function setPref(preferDefault, name, value) {
|
||||||
|
let branch = preferDefault
|
||||||
|
? Services.prefs.getDefaultBranch("")
|
||||||
|
: Services.prefs.getBranch("");
|
||||||
|
|
||||||
|
if (typeof value == "boolean") {
|
||||||
|
branch.setBoolPref(name, value);
|
||||||
|
} else if (typeof value == "string") {
|
||||||
|
if (value.startsWith("chrome://") && value.endsWith(".properties")) {
|
||||||
|
let valueLocal = Cc[
|
||||||
|
"@mozilla.org/pref-localizedstring;1"
|
||||||
|
].createInstance(Ci.nsIPrefLocalizedString);
|
||||||
|
valueLocal.data = value;
|
||||||
|
branch.setComplexValue(name, Ci.nsIPrefLocalizedString, valueLocal);
|
||||||
|
} else {
|
||||||
|
branch.setStringPref(name, value);
|
||||||
|
}
|
||||||
|
} else if (typeof value == "number" && Number.isInteger(value)) {
|
||||||
|
branch.setIntPref(name, value);
|
||||||
|
} else if (typeof value == "number" && Number.isFloat(value)) {
|
||||||
|
// Floats are set as char prefs, then retrieved using getFloatPref
|
||||||
|
branch.setCharPref(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function walkExtensionPrefs(extensionRoot) {
|
||||||
|
let prefFile = extensionRoot.clone();
|
||||||
|
let foundPrefStrings = [];
|
||||||
|
if (!prefFile.exists()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefFile.isDirectory()) {
|
||||||
|
prefFile.append("defaults");
|
||||||
|
prefFile.append("preferences");
|
||||||
|
if (!prefFile.exists() || !prefFile.isDirectory()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let unsortedFiles = [];
|
||||||
|
for (let file of fixIterator(prefFile.directoryEntries, Ci.nsIFile)) {
|
||||||
|
if (file.isFile() && file.leafName.toLowerCase().endsWith(".js")) {
|
||||||
|
unsortedFiles.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let file of unsortedFiles.sort((a, b) =>
|
||||||
|
a.path < b.path ? 1 : -1
|
||||||
|
)) {
|
||||||
|
foundPrefStrings.push(IOUtils.loadFileToString(file));
|
||||||
|
}
|
||||||
|
} else if (prefFile.isFile() && prefFile.leafName.endsWith("xpi")) {
|
||||||
|
let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(
|
||||||
|
Ci.nsIZipReader
|
||||||
|
);
|
||||||
|
zipReader.open(prefFile);
|
||||||
|
let entries = zipReader.findEntries("defaults/preferences/*.js");
|
||||||
|
let unsortedEntries = [];
|
||||||
|
while (entries.hasMore()) {
|
||||||
|
unsortedEntries.push(entries.getNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let entryName of unsortedEntries.sort().reverse()) {
|
||||||
|
let stream = zipReader.getInputStream(entryName);
|
||||||
|
let entrySize = zipReader.getEntry(entryName).realSize;
|
||||||
|
if (entrySize > 0) {
|
||||||
|
let content = NetUtil.readInputStreamToString(stream, entrySize, {
|
||||||
|
charset: "utf-8",
|
||||||
|
replacement: "?",
|
||||||
|
});
|
||||||
|
foundPrefStrings.push(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundPrefStrings;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sandbox = new Cu.Sandbox(null);
|
||||||
|
sandbox.pref = setPref.bind(undefined, true);
|
||||||
|
sandbox.user_pref = setPref.bind(undefined, false);
|
||||||
|
|
||||||
|
let prefDataStrings = walkExtensionPrefs(addonFile);
|
||||||
|
for (let prefDataString of prefDataStrings) {
|
||||||
|
try {
|
||||||
|
Cu.evalInSandbox(prefDataString, sandbox);
|
||||||
|
} catch (e) {
|
||||||
|
Cu.reportError(
|
||||||
|
"Error reading default prefs of addon " +
|
||||||
|
addonFile.leafName +
|
||||||
|
": " +
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: decide whether we need to warn the user/make addon authors to migrate away from these pref files.
|
||||||
|
if (prefDataStrings.length > 0) {
|
||||||
|
Deprecated.warning(addon.defaultLocale.name + " uses defaults/preferences/*.js files to load prefs",
|
||||||
|
"https://bugzilla.mozilla.org/show_bug.cgi?id=1414398");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register listening for windows getting opened that will run the specified callback function
|
||||||
|
* when a matching window is loaded.
|
||||||
|
*
|
||||||
|
* @param aID {String} Some identification of the caller, usually the extension ID.
|
||||||
|
* @param aExtensionHook {Object} The object describing the hook the caller wants to register.
|
||||||
|
* Members of the object can be (all optional, but one callback must be supplied):
|
||||||
|
* chromeURLs {Array} An array of strings of document URLs on which
|
||||||
|
* the given callback should run. If not specified,
|
||||||
|
* run on all windows.
|
||||||
|
* onLoadWindow {function} The callback function to run when window loads
|
||||||
|
* the matching document.
|
||||||
|
* onUnloadWindow {function} The callback function to run when window
|
||||||
|
* unloads the matching document.
|
||||||
|
* Both callbacks receive the matching window object as argument.
|
||||||
|
*
|
||||||
|
* @returns {boolean} True if the passed arguments were valid and the caller could be registered.
|
||||||
|
* False otherwise.
|
||||||
|
*/
|
||||||
|
registerWindowListener(aID, aExtensionHook) {
|
||||||
|
if (!aID) {
|
||||||
|
Cu.reportError("No extension ID provided for the window listener");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extensionHooks.has(aID)) {
|
||||||
|
Cu.reportError(
|
||||||
|
"Window listener for extension + '" + aID + "' already registered"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!("onLoadWindow" in aExtensionHook) &&
|
||||||
|
!("onUnloadWindow" in aExtensionHook)
|
||||||
|
) {
|
||||||
|
Cu.reportError(
|
||||||
|
"The extension + '" + aID + "' does not provide any callbacks"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
extensionHooks.set(aID, aExtensionHook);
|
||||||
|
|
||||||
|
// Add our global listener if there isn't one already
|
||||||
|
// (only when we have first caller).
|
||||||
|
if (extensionHooks.size == 1) {
|
||||||
|
Services.wm.addListener(this._windowListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openWindowList) {
|
||||||
|
// We already have a list of open windows, notify the caller about them.
|
||||||
|
openWindowList.forEach(domWindow =>
|
||||||
|
ExtensionSupport._checkAndRunMatchingExtensions(domWindow, "load", aID)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
openWindowList = new Set();
|
||||||
|
// Get the list of windows already open.
|
||||||
|
let windows = Services.wm.getEnumerator(null);
|
||||||
|
while (windows.hasMoreElements()) {
|
||||||
|
let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
|
||||||
|
if (domWindow.document.location.href === "about:blank") {
|
||||||
|
ExtensionSupport._waitForLoad(domWindow, aID);
|
||||||
|
} else {
|
||||||
|
ExtensionSupport._addToListAndNotify(domWindow, aID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister listening for windows for the given caller.
|
||||||
|
*
|
||||||
|
* @param aID {String} Some identification of the caller, usually the extension ID.
|
||||||
|
*
|
||||||
|
* @returns {boolean} True if the passed arguments were valid and the caller could be unregistered.
|
||||||
|
* False otherwise.
|
||||||
|
*/
|
||||||
|
unregisterWindowListener(aID) {
|
||||||
|
if (!aID) {
|
||||||
|
Cu.reportError("No extension ID provided for the window listener");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let windowListener = extensionHooks.get(aID);
|
||||||
|
if (!windowListener) {
|
||||||
|
Cu.reportError(
|
||||||
|
"Couldn't remove window listener for extension + '" + aID + "'"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
extensionHooks.delete(aID);
|
||||||
|
// Remove our global listener if there are no callers registered anymore.
|
||||||
|
if (extensionHooks.size == 0) {
|
||||||
|
Services.wm.removeListener(this._windowListener);
|
||||||
|
openWindowList.clear();
|
||||||
|
openWindowList = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
get openWindows() {
|
||||||
|
return openWindowList.values();
|
||||||
|
},
|
||||||
|
|
||||||
|
_windowListener: {
|
||||||
|
// nsIWindowMediatorListener functions
|
||||||
|
onOpenWindow(xulWindow) {
|
||||||
|
// A new window has opened.
|
||||||
|
let domWindow = xulWindow.docShell.domWindow;
|
||||||
|
|
||||||
|
// Here we pass no caller ID, so all registered callers get notified.
|
||||||
|
ExtensionSupport._waitForLoad(domWindow);
|
||||||
|
},
|
||||||
|
|
||||||
|
onCloseWindow(xulWindow) {
|
||||||
|
// One of the windows has closed.
|
||||||
|
let domWindow = xulWindow.docShell.domWindow;
|
||||||
|
openWindowList.delete(domWindow);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up listeners to run the callbacks on the given window.
|
||||||
|
*
|
||||||
|
* @param aWindow {nsIDOMWindow} The window to set up.
|
||||||
|
* @param aID {String} Optional. ID of the new caller that has registered right now.
|
||||||
|
*/
|
||||||
|
_waitForLoad(aWindow, aID) {
|
||||||
|
// Wait for the load event of the window. At that point
|
||||||
|
// aWindow.document.location.href will not be "about:blank" any more.
|
||||||
|
aWindow.addEventListener(
|
||||||
|
"load",
|
||||||
|
function() {
|
||||||
|
ExtensionSupport._addToListAndNotify(aWindow, aID);
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Once the window is fully loaded with the href referring to the XUL document,
|
||||||
|
* add it to our list, attach the "unload" listener to it and notify interested
|
||||||
|
* callers.
|
||||||
|
*
|
||||||
|
* @param aWindow {nsIDOMWindow} The window to process.
|
||||||
|
* @param aID {String} Optional. ID of the new caller that has registered right now.
|
||||||
|
*/
|
||||||
|
_addToListAndNotify(aWindow, aID) {
|
||||||
|
openWindowList.add(aWindow);
|
||||||
|
aWindow.addEventListener(
|
||||||
|
"unload",
|
||||||
|
function() {
|
||||||
|
ExtensionSupport._checkAndRunMatchingExtensions(aWindow, "unload");
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
ExtensionSupport._checkAndRunMatchingExtensions(aWindow, "load", aID);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the caller matches the given window and run its callback function.
|
||||||
|
*
|
||||||
|
* @param aWindow {nsIDOMWindow} The window to run the callbacks on.
|
||||||
|
* @param aEventType {String} Which callback to run if caller matches (load/unload).
|
||||||
|
* @param aID {String} Optional ID of the caller whose callback is to be run.
|
||||||
|
* If not given, all registered callers are notified.
|
||||||
|
*/
|
||||||
|
_checkAndRunMatchingExtensions(aWindow, aEventType, aID) {
|
||||||
|
if (aID) {
|
||||||
|
checkAndRunExtensionCode(extensionHooks.get(aID));
|
||||||
|
} else {
|
||||||
|
for (let extensionHook of extensionHooks.values()) {
|
||||||
|
checkAndRunExtensionCode(extensionHook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the single given caller matches the given window
|
||||||
|
* and run its callback function.
|
||||||
|
*
|
||||||
|
* @param aExtensionHook {Object} The object describing the hook the caller
|
||||||
|
* has registered.
|
||||||
|
*/
|
||||||
|
function checkAndRunExtensionCode(aExtensionHook) {
|
||||||
|
let windowChromeURL = aWindow.document.location.href;
|
||||||
|
// Check if extension applies to this document URL.
|
||||||
|
if (
|
||||||
|
"chromeURLs" in aExtensionHook &&
|
||||||
|
!aExtensionHook.chromeURLs.some(url => url == windowChromeURL)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the relevant callback.
|
||||||
|
switch (aEventType) {
|
||||||
|
case "load":
|
||||||
|
if ("onLoadWindow" in aExtensionHook) {
|
||||||
|
aExtensionHook.onLoadWindow(aWindow);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "unload":
|
||||||
|
if ("onUnloadWindow" in aExtensionHook) {
|
||||||
|
aExtensionHook.onUnloadWindow(aWindow);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
get registeredWindowListenerCount() {
|
||||||
|
return extensionHooks.size;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
AddonManager.addAddonListener(ExtensionSupport.loadedLegacyExtensions);
|
||||||
143
waterfox/browser/extensions/common/IOUtils.jsm
Normal file
143
waterfox/browser/extensions/common/IOUtils.jsm
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
const EXPORTED_SYMBOLS = ["IOUtils"];
|
||||||
|
|
||||||
|
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
var kStringBlockSize = 4096;
|
||||||
|
var kStreamBlockSize = 8192;
|
||||||
|
|
||||||
|
var IOUtils = {
|
||||||
|
/**
|
||||||
|
* Read a file containing ASCII text into a string.
|
||||||
|
*
|
||||||
|
* @param aFile An nsIFile representing the file to read or a string containing
|
||||||
|
* the file name of a file under user's profile.
|
||||||
|
* @returns A string containing the contents of the file, presumed to be ASCII
|
||||||
|
* text. If the file didn't exist, returns null.
|
||||||
|
*/
|
||||||
|
loadFileToString(aFile) {
|
||||||
|
let file;
|
||||||
|
if (!(aFile instanceof Ci.nsIFile)) {
|
||||||
|
file = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||||
|
file.append(aFile);
|
||||||
|
} else {
|
||||||
|
file = aFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
|
||||||
|
Ci.nsIFileInputStream
|
||||||
|
);
|
||||||
|
// PR_RDONLY
|
||||||
|
fstream.init(file, 0x01, 0, 0);
|
||||||
|
|
||||||
|
let sstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
|
||||||
|
Ci.nsIScriptableInputStream
|
||||||
|
);
|
||||||
|
sstream.init(fstream);
|
||||||
|
|
||||||
|
let data = "";
|
||||||
|
while (sstream.available()) {
|
||||||
|
data += sstream.read(kStringBlockSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
sstream.close();
|
||||||
|
fstream.close();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a string containing ASCII text into a file. The file will be overwritten
|
||||||
|
* and contain only the given text.
|
||||||
|
*
|
||||||
|
* @param aFile An nsIFile representing the file to write or a string containing
|
||||||
|
* the file name of a file under user's profile.
|
||||||
|
* @param aData The string to write.
|
||||||
|
* @param aPerms The octal file permissions for the created file. If unset
|
||||||
|
* the default of 0o600 is used.
|
||||||
|
*/
|
||||||
|
saveStringToFile(aFile, aData, aPerms = 0o600) {
|
||||||
|
let file;
|
||||||
|
if (!(aFile instanceof Ci.nsIFile)) {
|
||||||
|
file = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
||||||
|
file.append(aFile);
|
||||||
|
} else {
|
||||||
|
file = aFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
let foStream = Cc[
|
||||||
|
"@mozilla.org/network/safe-file-output-stream;1"
|
||||||
|
].createInstance(Ci.nsIFileOutputStream);
|
||||||
|
|
||||||
|
// PR_WRONLY + PR_CREATE_FILE + PR_TRUNCATE
|
||||||
|
foStream.init(file, 0x02 | 0x08 | 0x20, aPerms, 0);
|
||||||
|
// safe-file-output-stream appears to throw an error if it doesn't write everything at once
|
||||||
|
// so we won't worry about looping to deal with partial writes.
|
||||||
|
// In case we try to use this function for big files where buffering
|
||||||
|
// is needed we could use the implementation in saveStreamToFile().
|
||||||
|
foStream.write(aData, aData.length);
|
||||||
|
foStream.QueryInterface(Ci.nsISafeOutputStream).finish();
|
||||||
|
foStream.close();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the given input stream to a file.
|
||||||
|
*
|
||||||
|
* @param aIStream The input stream to save.
|
||||||
|
* @param aFile The file to which the stream is saved.
|
||||||
|
* @param aPerms The octal file permissions for the created file. If unset
|
||||||
|
* the default of 0o600 is used.
|
||||||
|
*/
|
||||||
|
saveStreamToFile(aIStream, aFile, aPerms = 0o600) {
|
||||||
|
if (!(aIStream instanceof Ci.nsIInputStream)) {
|
||||||
|
throw new Error("Invalid stream passed to saveStreamToFile");
|
||||||
|
}
|
||||||
|
if (!(aFile instanceof Ci.nsIFile)) {
|
||||||
|
throw new Error("Invalid file passed to saveStreamToFile");
|
||||||
|
}
|
||||||
|
|
||||||
|
let fstream = Cc[
|
||||||
|
"@mozilla.org/network/safe-file-output-stream;1"
|
||||||
|
].createInstance(Ci.nsIFileOutputStream);
|
||||||
|
let buffer = Cc[
|
||||||
|
"@mozilla.org/network/buffered-output-stream;1"
|
||||||
|
].createInstance(Ci.nsIBufferedOutputStream);
|
||||||
|
|
||||||
|
// Write the input stream to the file.
|
||||||
|
// PR_WRITE + PR_CREATE + PR_TRUNCATE
|
||||||
|
fstream.init(aFile, 0x04 | 0x08 | 0x20, aPerms, 0);
|
||||||
|
buffer.init(fstream, kStreamBlockSize);
|
||||||
|
|
||||||
|
buffer.writeFrom(aIStream, aIStream.available());
|
||||||
|
|
||||||
|
// Close the output streams.
|
||||||
|
if (buffer instanceof Ci.nsISafeOutputStream) {
|
||||||
|
buffer.finish();
|
||||||
|
} else {
|
||||||
|
buffer.close();
|
||||||
|
}
|
||||||
|
if (fstream instanceof Ci.nsISafeOutputStream) {
|
||||||
|
fstream.finish();
|
||||||
|
} else {
|
||||||
|
fstream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the input stream.
|
||||||
|
aIStream.close();
|
||||||
|
return aFile;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns size of system memory.
|
||||||
|
*/
|
||||||
|
getPhysicalMemorySize() {
|
||||||
|
return Services.sysinfo.getPropertyAsInt64("memsize");
|
||||||
|
},
|
||||||
|
};
|
||||||
599
waterfox/browser/extensions/common/Overlays.jsm
Normal file
599
waterfox/browser/extensions/common/Overlays.jsm
Normal file
@@ -0,0 +1,599 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load overlays in a similar way as XUL did for legacy XUL add-ons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
this.EXPORTED_SYMBOLS = ["Overlays"];
|
||||||
|
|
||||||
|
const { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm");
|
||||||
|
ChromeUtils.defineModuleGetter(
|
||||||
|
this,
|
||||||
|
"Services",
|
||||||
|
"resource://gre/modules/Services.jsm"
|
||||||
|
);
|
||||||
|
ChromeUtils.defineModuleGetter(
|
||||||
|
this,
|
||||||
|
"setTimeout",
|
||||||
|
"resource://gre/modules/Timer.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
let oconsole = new ConsoleAPI({
|
||||||
|
prefix: "Overlays.jsm",
|
||||||
|
consoleID: "overlays-jsm",
|
||||||
|
maxLogLevelPref: "extensions.overlayloader.loglevel",
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The overlays class, providing support for loading overlays like they used to work. This class
|
||||||
|
* should likely be called through its static method Overlays.load()
|
||||||
|
*/
|
||||||
|
class Overlays {
|
||||||
|
/**
|
||||||
|
* Load overlays for the given window using the overlay provider, which can for example be a
|
||||||
|
* ChromeManifest object.
|
||||||
|
*
|
||||||
|
* @param {ChromeManifest} overlayProvider The overlay provider that contains information
|
||||||
|
* about styles and overlays.
|
||||||
|
* @param {DOMWindow} window The window to load into
|
||||||
|
*/
|
||||||
|
static load(overlayProvider, window) {
|
||||||
|
let instance = new Overlays(overlayProvider, window);
|
||||||
|
|
||||||
|
let urls = overlayProvider.overlay.get(instance.location, false);
|
||||||
|
instance.load(urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the overlays instance. This class should be called via Overlays.load() instead.
|
||||||
|
*
|
||||||
|
* @param {ChromeManifest} overlayProvider The overlay provider that contains information
|
||||||
|
* about styles and overlays.
|
||||||
|
* @param {DOMWindow} window The window to load into
|
||||||
|
*/
|
||||||
|
constructor(overlayProvider, window) {
|
||||||
|
this.overlayProvider = overlayProvider;
|
||||||
|
this.window = window;
|
||||||
|
if (window.location.protocol == "about:") {
|
||||||
|
this.location = window.location.protocol + window.location.pathname;
|
||||||
|
} else {
|
||||||
|
this.location = window.location.origin + window.location.pathname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A shorthand to this.window.document
|
||||||
|
*/
|
||||||
|
get document() {
|
||||||
|
return this.window.document;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the given urls into the window, recursively loading further overlays as provided by the
|
||||||
|
* overlayProvider.
|
||||||
|
*
|
||||||
|
* @param {string[]} urls The urls to load
|
||||||
|
*/
|
||||||
|
load(urls) {
|
||||||
|
let unloadedOverlays = this._collectOverlays(this.document).concat(urls);
|
||||||
|
let forwardReferences = [];
|
||||||
|
let unloadedScripts = [];
|
||||||
|
let unloadedSheets = [];
|
||||||
|
this._toolbarsToResolve = [];
|
||||||
|
let xulStore = Services.xulStore;
|
||||||
|
this.persistedIDs = new Set();
|
||||||
|
|
||||||
|
// Load css styles from the registry
|
||||||
|
for (let sheet of this.overlayProvider.style.get(this.location, false)) {
|
||||||
|
unloadedSheets.push(sheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unloadedOverlays.length && !unloadedSheets.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (unloadedOverlays.length) {
|
||||||
|
let url = unloadedOverlays.shift();
|
||||||
|
let xhr = this.fetchOverlay(url);
|
||||||
|
let doc = xhr.responseXML;
|
||||||
|
|
||||||
|
oconsole.debug(`Applying ${url} to ${this.location}`);
|
||||||
|
|
||||||
|
// clean the document a bit
|
||||||
|
let emptyNodes = doc.evaluate(
|
||||||
|
"//text()[normalize-space(.) = '']",
|
||||||
|
doc,
|
||||||
|
null,
|
||||||
|
7,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
for (let i = 0, len = emptyNodes.snapshotLength; i < len; ++i) {
|
||||||
|
let node = emptyNodes.snapshotItem(i);
|
||||||
|
node.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
let commentNodes = doc.evaluate("//comment()", doc, null, 7, null);
|
||||||
|
for (let i = 0, len = commentNodes.snapshotLength; i < len; ++i) {
|
||||||
|
let node = commentNodes.snapshotItem(i);
|
||||||
|
node.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load css styles from the registry
|
||||||
|
for (let sheet of this.overlayProvider.style.get(url, false)) {
|
||||||
|
unloadedSheets.push(sheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load css processing instructions from the overlay
|
||||||
|
let stylesheets = doc.evaluate(
|
||||||
|
"/processing-instruction('xml-stylesheet')",
|
||||||
|
doc,
|
||||||
|
null,
|
||||||
|
7,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
for (let i = 0, len = stylesheets.snapshotLength; i < len; ++i) {
|
||||||
|
let node = stylesheets.snapshotItem(i);
|
||||||
|
let match = node.nodeValue.match(/href=["']([^"']*)["']/);
|
||||||
|
if (match) {
|
||||||
|
unloadedSheets.push(new URL(match[1], node.baseURI).href);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare loading further nested xul overlays from the overlay
|
||||||
|
unloadedOverlays.push(...this._collectOverlays(doc));
|
||||||
|
|
||||||
|
// Prepare loading further nested xul overlays from the registry
|
||||||
|
for (let overlayUrl of this.overlayProvider.overlay.get(url, false)) {
|
||||||
|
unloadedOverlays.push(overlayUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run through all overlay nodes on the first level (hookup nodes). Scripts will be deferred
|
||||||
|
// until later for simplicity (c++ code seems to process them earlier?).
|
||||||
|
for (let node of doc.documentElement.children) {
|
||||||
|
if (node.localName == "script") {
|
||||||
|
unloadedScripts.push(node);
|
||||||
|
} else {
|
||||||
|
forwardReferences.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ids = xulStore.getIDsEnumerator(this.location);
|
||||||
|
while (ids.hasMore()) {
|
||||||
|
this.persistedIDs.add(ids.getNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, all (recursive) overlays are loaded. Unloaded scripts and sheets are ready and
|
||||||
|
// in order, and forward references are good to process.
|
||||||
|
let previous = 0;
|
||||||
|
while (forwardReferences.length && forwardReferences.length != previous) {
|
||||||
|
previous = forwardReferences.length;
|
||||||
|
let unresolved = [];
|
||||||
|
|
||||||
|
for (let ref of forwardReferences) {
|
||||||
|
if (!this._resolveForwardReference(ref)) {
|
||||||
|
unresolved.push(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardReferences = unresolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forwardReferences.length) {
|
||||||
|
oconsole.warn(
|
||||||
|
`Could not resolve ${forwardReferences.length} references`,
|
||||||
|
forwardReferences
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading the sheets now to avoid race conditions with xbl bindings
|
||||||
|
for (let sheet of unloadedSheets) {
|
||||||
|
this.loadCSS(sheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._decksToResolve = new Map();
|
||||||
|
for (let id of this.persistedIDs.values()) {
|
||||||
|
let element = this.document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
let attrNames = xulStore.getAttributeEnumerator(this.location, id);
|
||||||
|
while (attrNames.hasMore()) {
|
||||||
|
let attrName = attrNames.getNext();
|
||||||
|
let attrValue = xulStore.getValue(this.location, id, attrName);
|
||||||
|
if (attrName == "selectedIndex" && element.localName == "deck") {
|
||||||
|
this._decksToResolve.set(element, attrValue);
|
||||||
|
} else {
|
||||||
|
element.setAttribute(attrName, attrValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've resolved all the forward references we can, we can now go ahead and load the scripts
|
||||||
|
let deferredLoad = [];
|
||||||
|
for (let script of unloadedScripts) {
|
||||||
|
deferredLoad.push(...this.loadScript(script));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.document.readyState == "complete") {
|
||||||
|
let sheet;
|
||||||
|
let overlayTrigger = this.document.createXULElement("overlayTrigger");
|
||||||
|
overlayTrigger.addEventListener(
|
||||||
|
"bindingattached",
|
||||||
|
() => {
|
||||||
|
oconsole.debug("XBL binding attached, continuing with load");
|
||||||
|
if (sheet) {
|
||||||
|
sheet.remove();
|
||||||
|
}
|
||||||
|
overlayTrigger.remove();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this._finish();
|
||||||
|
|
||||||
|
// Now execute load handlers since we are done loading scripts
|
||||||
|
let bubbles = [];
|
||||||
|
for (let { listener, useCapture } of deferredLoad) {
|
||||||
|
if (useCapture) {
|
||||||
|
this._fireEventListener(listener);
|
||||||
|
} else {
|
||||||
|
bubbles.push(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let listener of bubbles) {
|
||||||
|
this._fireEventListener(listener);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
this.document.documentElement.appendChild(overlayTrigger);
|
||||||
|
if (overlayTrigger.parentNode) {
|
||||||
|
sheet = this.loadCSS("chrome://messenger/content/overlayBindings.css");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.document.defaultView.addEventListener(
|
||||||
|
"load",
|
||||||
|
this._finish.bind(this),
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_finish() {
|
||||||
|
for (let [deck, selectedIndex] of this._decksToResolve.entries()) {
|
||||||
|
deck.setAttribute("selectedIndex", selectedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let bar of this._toolbarsToResolve) {
|
||||||
|
let currentset = Services.xulStore.getValue(
|
||||||
|
this.location,
|
||||||
|
bar.id,
|
||||||
|
"currentset"
|
||||||
|
);
|
||||||
|
if (currentset) {
|
||||||
|
bar.currentSet = currentset;
|
||||||
|
} else if (bar.getAttribute("defaultset")) {
|
||||||
|
bar.currentSet = bar.getAttribute("defaultset");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the overlays referenced by processing instruction on a document.
|
||||||
|
*
|
||||||
|
* @param {DOMDocument} document The document to read instuctions from
|
||||||
|
* @returns {string[]} URLs of the overlays from the document
|
||||||
|
*/
|
||||||
|
_collectOverlays(doc) {
|
||||||
|
let urls = [];
|
||||||
|
let instructions = doc.evaluate(
|
||||||
|
"/processing-instruction('xul-overlay')",
|
||||||
|
doc,
|
||||||
|
null,
|
||||||
|
7,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
for (let i = 0, len = instructions.snapshotLength; i < len; ++i) {
|
||||||
|
let node = instructions.snapshotItem(i);
|
||||||
|
let match = node.nodeValue.match(/href=["']([^"']*)["']/);
|
||||||
|
if (match) {
|
||||||
|
urls.push(match[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fires a "load" event for the given listener, using the current window
|
||||||
|
*
|
||||||
|
* @param {EventListener|Function} listener The event listener to call
|
||||||
|
*/
|
||||||
|
_fireEventListener(listener) {
|
||||||
|
let fakeEvent = new this.window.UIEvent("load", { view: this.window });
|
||||||
|
if (typeof listener == "function") {
|
||||||
|
listener(fakeEvent);
|
||||||
|
} else if (listener && typeof listener == "object") {
|
||||||
|
listener.handleEvent(fakeEvent);
|
||||||
|
} else {
|
||||||
|
oconsole.error("Unknown listener type", listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves forward references for the given node. If the node exists in the target document, it
|
||||||
|
* is merged in with the target node. If the node has no id it is inserted at documentElement
|
||||||
|
* level.
|
||||||
|
*
|
||||||
|
* @param {Element} node The DOM Element to resolve in the target document.
|
||||||
|
* @returns {boolean} True, if the node was merged/inserted, false otherwise
|
||||||
|
*/
|
||||||
|
_resolveForwardReference(node) {
|
||||||
|
if (node.id) {
|
||||||
|
let target = this.document.getElementById(node.id);
|
||||||
|
if (node.localName == "toolbarpalette") {
|
||||||
|
let box;
|
||||||
|
if (target) {
|
||||||
|
box = target.closest("toolbox");
|
||||||
|
} else {
|
||||||
|
// These vanish from the document but still exist via the palette property
|
||||||
|
let boxes = [...this.document.getElementsByTagName("toolbox")];
|
||||||
|
box = boxes.find(box => box.palette && box.palette.id == node.id);
|
||||||
|
let palette = box ? box.palette : null;
|
||||||
|
|
||||||
|
if (!palette) {
|
||||||
|
oconsole.debug(
|
||||||
|
`The palette for ${node.id} could not be found, deferring to later`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
target = palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._toolbarsToResolve.push(
|
||||||
|
...box.querySelectorAll('toolbar:not([type="menubar"])')
|
||||||
|
);
|
||||||
|
} else if (!target) {
|
||||||
|
oconsole.debug(
|
||||||
|
`The node ${node.id} could not be found, deferring to later`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._mergeElement(target, node);
|
||||||
|
} else {
|
||||||
|
this._insertElement(this.document.documentElement, node);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert the node in the given parent, observing the insertbefore/insertafter/position attributes
|
||||||
|
*
|
||||||
|
* @param {Element} parent The parent element to insert the node into.
|
||||||
|
* @param {Element} node The node to insert.
|
||||||
|
*/
|
||||||
|
_insertElement(parent, node) {
|
||||||
|
// These elements need their values set before they are added to
|
||||||
|
// the document, or bad things happen.
|
||||||
|
for (let element of node.querySelectorAll("menulist, radiogroup")) {
|
||||||
|
if (element.id && this.persistedIDs.has(element.id)) {
|
||||||
|
element.setAttribute(
|
||||||
|
"value",
|
||||||
|
Services.xulStore.getValue(this.location, element.id, "value")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let wasInserted = false;
|
||||||
|
let pos = node.getAttribute("insertafter");
|
||||||
|
let after = true;
|
||||||
|
|
||||||
|
if (!pos) {
|
||||||
|
pos = node.getAttribute("insertbefore");
|
||||||
|
after = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos) {
|
||||||
|
for (let id of pos.split(",")) {
|
||||||
|
let targetchild = this.document.getElementById(id);
|
||||||
|
if (targetchild && targetchild.parentNode == parent) {
|
||||||
|
parent.insertBefore(
|
||||||
|
node,
|
||||||
|
after ? targetchild.nextSibling : targetchild
|
||||||
|
);
|
||||||
|
wasInserted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wasInserted) {
|
||||||
|
// position is 1-based
|
||||||
|
let position = parseInt(node.getAttribute("position"), 10);
|
||||||
|
if (position > 0 && position - 1 <= parent.childNodes.length) {
|
||||||
|
parent.insertBefore(node, parent.childNodes[position - 1]);
|
||||||
|
wasInserted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wasInserted) {
|
||||||
|
parent.appendChild(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the node into the target, adhering to the removeelement attribute, merging further
|
||||||
|
* attributes into the target node, and merging children as appropriate for xul nodes. If a child
|
||||||
|
* has an id, it will be searched in the target document and recursively merged.
|
||||||
|
*
|
||||||
|
* @param {Element} target The node to merge into
|
||||||
|
* @param {Element} node The node that is being merged
|
||||||
|
*/
|
||||||
|
_mergeElement(target, node) {
|
||||||
|
for (let attribute of node.attributes) {
|
||||||
|
if (attribute.name == "id") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attribute.name == "removeelement" && attribute.value == "true") {
|
||||||
|
target.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.setAttributeNS(
|
||||||
|
attribute.namespaceURI,
|
||||||
|
attribute.name,
|
||||||
|
attribute.value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0, len = node.childElementCount; i < len; i++) {
|
||||||
|
let child = node.firstElementChild;
|
||||||
|
child.remove();
|
||||||
|
|
||||||
|
let elementInDocument = child.id
|
||||||
|
? this.document.getElementById(child.id)
|
||||||
|
: null;
|
||||||
|
let parentId = elementInDocument ? elementInDocument.parentNode.id : null;
|
||||||
|
|
||||||
|
if (parentId && parentId == target.id) {
|
||||||
|
this._mergeElement(elementInDocument, child);
|
||||||
|
} else {
|
||||||
|
this._insertElement(target, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the overlay from the given chrome:// or resource:// URL. This happen synchronously so
|
||||||
|
* we have a chance to complete before the load event.
|
||||||
|
*
|
||||||
|
* @param {string} srcUrl The URL to load
|
||||||
|
* @returns {XMLHttpRequest} The completed XHR.
|
||||||
|
*/
|
||||||
|
fetchOverlay(srcUrl) {
|
||||||
|
if (!srcUrl.startsWith("chrome://") && !srcUrl.startsWith("resource://")) {
|
||||||
|
throw new Error(
|
||||||
|
"May only load overlays from chrome:// or resource:// uris"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
xhr.overrideMimeType("application/xml");
|
||||||
|
xhr.open("GET", srcUrl, false);
|
||||||
|
|
||||||
|
// Elevate the request, so DTDs will work. Should not be a security issue since we
|
||||||
|
// only load chrome, resource and file URLs, and that is our privileged chrome package.
|
||||||
|
try {
|
||||||
|
xhr.channel.owner = Services.scriptSecurityManager.getSystemPrincipal();
|
||||||
|
} catch (ex) {
|
||||||
|
oconsole.error(
|
||||||
|
"Failed to set system principal while fetching overlay " + srcUrl
|
||||||
|
);
|
||||||
|
xhr.close();
|
||||||
|
throw new Error("Failed to set system principal");
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.send(null);
|
||||||
|
return xhr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads scripts described by the given script node. The node can either have a src attribute, or
|
||||||
|
* be an inline script with textContent.
|
||||||
|
*
|
||||||
|
* @param {Element} node The <script> element to load the script from
|
||||||
|
* @returns {Object[]} An object with listener and useCapture,
|
||||||
|
* describing load handlers the script creates
|
||||||
|
* when first run.
|
||||||
|
*/
|
||||||
|
loadScript(node) {
|
||||||
|
let deferredLoad = [];
|
||||||
|
|
||||||
|
let oldAddEventListener = this.window.addEventListener;
|
||||||
|
if (this.document.readyState == "complete") {
|
||||||
|
this.window.addEventListener = function(
|
||||||
|
type,
|
||||||
|
listener,
|
||||||
|
useCapture,
|
||||||
|
...args
|
||||||
|
) {
|
||||||
|
if (type == "load") {
|
||||||
|
if (typeof useCapture == "object") {
|
||||||
|
useCapture = useCapture.capture;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof useCapture == "undefined") {
|
||||||
|
useCapture = true;
|
||||||
|
}
|
||||||
|
deferredLoad.push({ listener, useCapture });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return oldAddEventListener.call(
|
||||||
|
this,
|
||||||
|
type,
|
||||||
|
listener,
|
||||||
|
useCapture,
|
||||||
|
...args
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.hasAttribute("src")) {
|
||||||
|
let url = new URL(node.getAttribute("src"), node.baseURI).href;
|
||||||
|
oconsole.debug(`Loading script ${url} into ${this.window.location}`);
|
||||||
|
try {
|
||||||
|
Services.scriptloader.loadSubScript(url, this.window);
|
||||||
|
} catch (ex) {
|
||||||
|
Cu.reportError(ex);
|
||||||
|
}
|
||||||
|
} else if (node.textContent) {
|
||||||
|
oconsole.debug(`Loading eval'd script into ${this.window.location}`);
|
||||||
|
try {
|
||||||
|
let dataURL =
|
||||||
|
"data:application/javascript," + encodeURIComponent(node.textContent);
|
||||||
|
// It would be great if we could have script errors show the right url, but for now
|
||||||
|
// loadSubScript will have to do.
|
||||||
|
Services.scriptloader.loadSubScript(dataURL, this.window);
|
||||||
|
} catch (ex) {
|
||||||
|
Cu.reportError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.document.readyState == "complete") {
|
||||||
|
this.window.addEventListener = oldAddEventListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This works because we only care about immediately executed addEventListener calls and
|
||||||
|
// loadSubScript is synchronous. Everyone else should be checking readyState anyway.
|
||||||
|
return deferredLoad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the CSS stylesheet from the given url
|
||||||
|
*
|
||||||
|
* @param {string} url The url to load from
|
||||||
|
* @returns {Element} An HTML link element for this stylesheet
|
||||||
|
*/
|
||||||
|
loadCSS(url) {
|
||||||
|
oconsole.debug(`Loading ${url} into ${this.window.location}`);
|
||||||
|
|
||||||
|
// domWindowUtils.loadSheetUsingURIString doesn't record the sheet in document.styleSheets,
|
||||||
|
// adding a html link element seems to do so.
|
||||||
|
let link = this.document.createElementNS(
|
||||||
|
"http://www.w3.org/1999/xhtml",
|
||||||
|
"link"
|
||||||
|
);
|
||||||
|
link.setAttribute("rel", "stylesheet");
|
||||||
|
link.setAttribute("type", "text/css");
|
||||||
|
link.setAttribute("href", url);
|
||||||
|
|
||||||
|
this.document.documentElement.appendChild(link);
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
}
|
||||||
1724
waterfox/browser/extensions/common/RDFDataSource.jsm
Normal file
1724
waterfox/browser/extensions/common/RDFDataSource.jsm
Normal file
File diff suppressed because it is too large
Load Diff
138
waterfox/browser/extensions/common/RDFManifestConverter.jsm
Normal file
138
waterfox/browser/extensions/common/RDFManifestConverter.jsm
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["InstallRDF"];
|
||||||
|
|
||||||
|
ChromeUtils.defineModuleGetter(
|
||||||
|
this,
|
||||||
|
"RDFDataSource",
|
||||||
|
"resource:///modules/RDFDataSource.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest";
|
||||||
|
|
||||||
|
function EM_R(aProperty) {
|
||||||
|
return `http://www.mozilla.org/2004/em-rdf#${aProperty}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue(literal) {
|
||||||
|
return literal && literal.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProperty(resource, property) {
|
||||||
|
return getValue(resource.getProperty(EM_R(property)));
|
||||||
|
}
|
||||||
|
|
||||||
|
class Manifest {
|
||||||
|
constructor(ds) {
|
||||||
|
this.ds = ds;
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadFromString(text) {
|
||||||
|
return new this(RDFDataSource.loadFromString(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadFromBuffer(buffer) {
|
||||||
|
return new this(RDFDataSource.loadFromBuffer(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
static async loadFromFile(uri) {
|
||||||
|
return new this(await RDFDataSource.loadFromFile(uri));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InstallRDF extends Manifest {
|
||||||
|
_readProps(source, obj, props) {
|
||||||
|
for (let prop of props) {
|
||||||
|
let val = getProperty(source, prop);
|
||||||
|
if (val != null) {
|
||||||
|
obj[prop] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_readArrayProp(source, obj, prop, target, decode = getValue) {
|
||||||
|
let result = Array.from(source.getObjects(EM_R(prop)), target =>
|
||||||
|
decode(target)
|
||||||
|
);
|
||||||
|
if (result.length) {
|
||||||
|
obj[target] = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_readArrayProps(source, obj, props, decode = getValue) {
|
||||||
|
for (let [prop, target] of Object.entries(props)) {
|
||||||
|
this._readArrayProp(source, obj, prop, target, decode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_readLocaleStrings(source, obj) {
|
||||||
|
this._readProps(source, obj, [
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"creator",
|
||||||
|
"homepageURL",
|
||||||
|
]);
|
||||||
|
this._readArrayProps(source, obj, {
|
||||||
|
locale: "locales",
|
||||||
|
developer: "developers",
|
||||||
|
translator: "translators",
|
||||||
|
contributor: "contributors",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
decode() {
|
||||||
|
let root = this.ds.getResource(RDFURI_INSTALL_MANIFEST_ROOT);
|
||||||
|
let result = {};
|
||||||
|
|
||||||
|
let props = [
|
||||||
|
"id",
|
||||||
|
"version",
|
||||||
|
"type",
|
||||||
|
"updateURL",
|
||||||
|
"optionsURL",
|
||||||
|
"optionsType",
|
||||||
|
"aboutURL",
|
||||||
|
"iconURL",
|
||||||
|
"bootstrap",
|
||||||
|
"unpack",
|
||||||
|
"strictCompatibility",
|
||||||
|
];
|
||||||
|
this._readProps(root, result, props);
|
||||||
|
|
||||||
|
let decodeTargetApplication = source => {
|
||||||
|
let app = {};
|
||||||
|
this._readProps(source, app, ["id", "minVersion", "maxVersion"]);
|
||||||
|
return app;
|
||||||
|
};
|
||||||
|
|
||||||
|
let decodeLocale = source => {
|
||||||
|
let localized = {};
|
||||||
|
this._readLocaleStrings(source, localized);
|
||||||
|
return localized;
|
||||||
|
};
|
||||||
|
|
||||||
|
this._readLocaleStrings(root, result);
|
||||||
|
|
||||||
|
this._readArrayProps(root, result, { targetPlatform: "targetPlatforms" });
|
||||||
|
this._readArrayProps(
|
||||||
|
root,
|
||||||
|
result,
|
||||||
|
{ targetApplication: "targetApplications" },
|
||||||
|
decodeTargetApplication
|
||||||
|
);
|
||||||
|
this._readArrayProps(
|
||||||
|
root,
|
||||||
|
result,
|
||||||
|
{ localized: "localized" },
|
||||||
|
decodeLocale
|
||||||
|
);
|
||||||
|
this._readArrayProps(root, result, { dependency: "dependencies" }, source =>
|
||||||
|
getProperty(source, "id")
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
132
waterfox/browser/extensions/common/iteratorUtils.jsm
Normal file
132
waterfox/browser/extensions/common/iteratorUtils.jsm
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains helper methods for dealing with XPCOM iterators (arrays
|
||||||
|
* and enumerators) in JS-friendly ways.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const EXPORTED_SYMBOLS = ["fixIterator", "toXPCOMArray", "toArray"];
|
||||||
|
|
||||||
|
var JS_HAS_SYMBOLS = typeof Symbol === "function";
|
||||||
|
var ITERATOR_SYMBOL = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function will take a number of objects and convert them to an array.
|
||||||
|
*
|
||||||
|
* Currently, we support the following objects:
|
||||||
|
* Anything you can for (let x of aObj) on
|
||||||
|
* (e.g. toArray(fixIterator(enum))[4],
|
||||||
|
* also a NodeList from element.children)
|
||||||
|
*
|
||||||
|
* @param aObj The object to convert
|
||||||
|
*/
|
||||||
|
function toArray(aObj) {
|
||||||
|
// Iterable object
|
||||||
|
if (ITERATOR_SYMBOL in aObj) {
|
||||||
|
return Array.from(aObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// New style generator function
|
||||||
|
if (
|
||||||
|
typeof aObj == "function" &&
|
||||||
|
typeof aObj.constructor == "function" &&
|
||||||
|
aObj.constructor.name == "GeneratorFunction"
|
||||||
|
) {
|
||||||
|
return [...aObj()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// We got something unexpected, notify the caller loudly.
|
||||||
|
throw new Error(
|
||||||
|
"An unsupported object sent to toArray: " +
|
||||||
|
("toString" in aObj ? aObj.toString() : aObj)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a JS array, JS iterator, or one of a variety of XPCOM collections or
|
||||||
|
* iterators, return a JS iterator suitable for use in a for...of expression.
|
||||||
|
*
|
||||||
|
* Currently, we support the following types of XPCOM iterators:
|
||||||
|
* nsIArray
|
||||||
|
* nsISimpleEnumerator
|
||||||
|
*
|
||||||
|
* Note that old-style JS iterators are explicitly not supported in this
|
||||||
|
* method, as they are going away.
|
||||||
|
*
|
||||||
|
* @param aEnum the enumerator to convert
|
||||||
|
* @param aIface (optional) an interface to QI each object to prior to
|
||||||
|
* returning
|
||||||
|
*
|
||||||
|
* @note This returns an object that can be used in 'for...of' loops.
|
||||||
|
* Do not use 'for each...in' or 'for...in'.
|
||||||
|
* This does *not* return an Array object. To create such an array, use
|
||||||
|
* let array = toArray(fixIterator(xpcomEnumerator));
|
||||||
|
*/
|
||||||
|
function fixIterator(aEnum, aIface) {
|
||||||
|
// If the input is an array, nsISimpleEnumerator or something that sports Symbol.iterator,
|
||||||
|
// then the original input is sufficient to directly return. However, if we want
|
||||||
|
// to support the aIface parameter, we need to do a lazy version of
|
||||||
|
// Array.prototype.map.
|
||||||
|
if (
|
||||||
|
Array.isArray(aEnum) ||
|
||||||
|
aEnum instanceof Ci.nsISimpleEnumerator ||
|
||||||
|
ITERATOR_SYMBOL in aEnum
|
||||||
|
) {
|
||||||
|
if (!aIface) {
|
||||||
|
return aEnum[ITERATOR_SYMBOL]();
|
||||||
|
}
|
||||||
|
return (function*() {
|
||||||
|
for (let o of aEnum) {
|
||||||
|
yield o.QueryInterface(aIface);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
let face = aIface || Ci.nsISupports;
|
||||||
|
// Figure out which kind of array object we have.
|
||||||
|
// First try nsIArray (covers nsIMutableArray too).
|
||||||
|
if (aEnum instanceof Ci.nsIArray) {
|
||||||
|
return (function*() {
|
||||||
|
let count = aEnum.length;
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
yield aEnum.queryElementAt(i, face);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We got something unexpected, notify the caller loudly.
|
||||||
|
throw new Error(
|
||||||
|
"An unsupported object sent to fixIterator: " +
|
||||||
|
("toString" in aEnum ? aEnum.toString() : aEnum)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function takes an Array object and returns an XPCOM array
|
||||||
|
* of the desired type. It will *not* work if you extend Array.prototype.
|
||||||
|
*
|
||||||
|
* @param aArray the array (anything fixIterator supports) to convert to an XPCOM array
|
||||||
|
* @param aInterface the type of XPCOM array to convert
|
||||||
|
*
|
||||||
|
* @note The returned array is *not* dynamically updated. Changes made to the
|
||||||
|
* JS array after a call to this function will not be reflected in the
|
||||||
|
* XPCOM array.
|
||||||
|
*/
|
||||||
|
function toXPCOMArray(aArray, aInterface) {
|
||||||
|
if (aInterface.equals(Ci.nsIMutableArray)) {
|
||||||
|
let mutableArray = Cc["@mozilla.org/array;1"].createInstance(
|
||||||
|
Ci.nsIMutableArray
|
||||||
|
);
|
||||||
|
for (let item of fixIterator(aArray)) {
|
||||||
|
mutableArray.appendElement(item);
|
||||||
|
}
|
||||||
|
return mutableArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We got something unexpected, notify the caller loudly.
|
||||||
|
throw new Error(
|
||||||
|
"An unsupported interface requested from toXPCOMArray: " + aInterface
|
||||||
|
);
|
||||||
|
}
|
||||||
21
waterfox/browser/extensions/common/moz.build
Normal file
21
waterfox/browser/extensions/common/moz.build
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# vim: set filetype=python:
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
EXTRA_JS_MODULES += [
|
||||||
|
"BootstrapLoader.jsm",
|
||||||
|
"ChromeManifest.jsm",
|
||||||
|
"ExtensionSupport.jsm",
|
||||||
|
"IOUtils.jsm",
|
||||||
|
"iteratorUtils.jsm",
|
||||||
|
"Overlays.jsm",
|
||||||
|
"RDFDataSource.jsm",
|
||||||
|
"RDFManifestConverter.jsm",
|
||||||
|
]
|
||||||
|
|
||||||
|
LOCAL_INCLUDES += ["/netwerk/base"]
|
||||||
|
|
||||||
|
FINAL_LIBRARY = "xul"
|
||||||
|
|
||||||
|
XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell/xpcshell.ini"]
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
extends: "plugin:mozilla/xpcshell-test",
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
"func-names": "off",
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["monitor"];
|
||||||
|
|
||||||
|
function notify(event, originalMethod, data, reason) {
|
||||||
|
let info = {
|
||||||
|
event,
|
||||||
|
data: Object.assign({}, data, {
|
||||||
|
resourceURI: data.resourceURI.spec,
|
||||||
|
}),
|
||||||
|
reason,
|
||||||
|
};
|
||||||
|
|
||||||
|
let subject = { wrappedJSObject: { data } };
|
||||||
|
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
subject,
|
||||||
|
"bootstrapmonitor-event",
|
||||||
|
JSON.stringify(info)
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the bootstrap scope already declares a method call it
|
||||||
|
if (originalMethod) {
|
||||||
|
originalMethod(data, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows a simple one-line bootstrap script:
|
||||||
|
// Components.utils.import("resource://xpcshelldata/bootstrapmonitor.jsm").monitor(this);
|
||||||
|
var monitor = function(
|
||||||
|
scope,
|
||||||
|
methods = ["install", "startup", "shutdown", "uninstall"]
|
||||||
|
) {
|
||||||
|
for (let event of methods) {
|
||||||
|
scope[event] = notify.bind(null, event, scope[event]);
|
||||||
|
}
|
||||||
|
};
|
||||||
1716
waterfox/browser/extensions/common/test/xpcshell/head_addons.js
Normal file
1716
waterfox/browser/extensions/common/test/xpcshell/head_addons.js
Normal file
File diff suppressed because it is too large
Load Diff
1174
waterfox/browser/extensions/common/test/xpcshell/test_bootstrap.js
Normal file
1174
waterfox/browser/extensions/common/test/xpcshell/test_bootstrap.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
|
||||||
|
|
||||||
|
const ADDONS = {
|
||||||
|
test_bootstrap_const: {
|
||||||
|
"install.rdf": createInstallRDF({
|
||||||
|
id: "bootstrap@tests.mozilla.org",
|
||||||
|
}),
|
||||||
|
"bootstrap.js":
|
||||||
|
'var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");\n\nconst install = function() {\n Services.obs.notifyObservers(null, "addon-install");\n};\n',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
add_task(async function() {
|
||||||
|
await promiseStartupManager();
|
||||||
|
|
||||||
|
let sawInstall = false;
|
||||||
|
Services.obs.addObserver(function() {
|
||||||
|
sawInstall = true;
|
||||||
|
}, "addon-install");
|
||||||
|
|
||||||
|
await AddonTestUtils.promiseInstallXPI(ADDONS.test_bootstrap_const);
|
||||||
|
|
||||||
|
ok(sawInstall);
|
||||||
|
});
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// This verifies that bootstrap.js has the expected globals defined
|
||||||
|
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
|
||||||
|
|
||||||
|
const ADDONS = {
|
||||||
|
bootstrap_globals: {
|
||||||
|
"install.rdf": createInstallRDF({
|
||||||
|
id: "bootstrap_globals@tests.mozilla.org",
|
||||||
|
}),
|
||||||
|
"bootstrap.js": String.raw`var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
|
var seenGlobals = new Set();
|
||||||
|
var scope = this;
|
||||||
|
function checkGlobal(name, type) {
|
||||||
|
if (scope[name] && typeof(scope[name]) == type)
|
||||||
|
seenGlobals.add(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrapped = {};
|
||||||
|
Services.obs.notifyObservers({ wrappedJSObject: wrapped }, "bootstrap-request-globals");
|
||||||
|
for (let [name, type] of wrapped.expectedGlobals) {
|
||||||
|
checkGlobal(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startup(data, reason) {
|
||||||
|
Services.obs.notifyObservers({ wrappedJSObject: seenGlobals }, "bootstrap-seen-globals");
|
||||||
|
}
|
||||||
|
|
||||||
|
function install(data, reason) {}
|
||||||
|
function shutdown(data, reason) {}
|
||||||
|
function uninstall(data, reason) {}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const EXPECTED_GLOBALS = [["console", "object"]];
|
||||||
|
|
||||||
|
async function run_test() {
|
||||||
|
do_test_pending();
|
||||||
|
await promiseStartupManager();
|
||||||
|
let sawGlobals = false;
|
||||||
|
|
||||||
|
Services.obs.addObserver(function(subject) {
|
||||||
|
subject.wrappedJSObject.expectedGlobals = EXPECTED_GLOBALS;
|
||||||
|
}, "bootstrap-request-globals");
|
||||||
|
|
||||||
|
Services.obs.addObserver(function({ wrappedJSObject: seenGlobals }) {
|
||||||
|
for (let [name] of EXPECTED_GLOBALS) {
|
||||||
|
Assert.ok(seenGlobals.has(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
sawGlobals = true;
|
||||||
|
}, "bootstrap-seen-globals");
|
||||||
|
|
||||||
|
await AddonTestUtils.promiseInstallXPI(ADDONS.bootstrap_globals);
|
||||||
|
Assert.ok(sawGlobals);
|
||||||
|
await promiseShutdownManager();
|
||||||
|
do_test_finished();
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const ADDON = {
|
||||||
|
"install.rdf": createInstallRDF({
|
||||||
|
id: "bug675371@tests.mozilla.org",
|
||||||
|
}),
|
||||||
|
"chrome.manifest": `content bug675371 .`,
|
||||||
|
"test.js": `var active = true;`,
|
||||||
|
};
|
||||||
|
|
||||||
|
add_task(async function run_test() {
|
||||||
|
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
|
||||||
|
await promiseStartupManager();
|
||||||
|
});
|
||||||
|
|
||||||
|
function checkActive(expected) {
|
||||||
|
let target = { active: false };
|
||||||
|
let load = () => {
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
"chrome://bug675371/content/test.js",
|
||||||
|
target
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (expected) {
|
||||||
|
load();
|
||||||
|
} else {
|
||||||
|
Assert.throws(load, /Error opening input stream/);
|
||||||
|
}
|
||||||
|
equal(target.active, expected, "Manifest is active?");
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function test() {
|
||||||
|
let { addon } = await AddonTestUtils.promiseInstallXPI(ADDON);
|
||||||
|
|
||||||
|
Assert.ok(addon.isActive);
|
||||||
|
|
||||||
|
// Tests that chrome.manifest is registered when the addon is installed.
|
||||||
|
checkActive(true);
|
||||||
|
|
||||||
|
await addon.disable();
|
||||||
|
checkActive(false);
|
||||||
|
|
||||||
|
await addon.enable();
|
||||||
|
checkActive(true);
|
||||||
|
|
||||||
|
await promiseShutdownManager();
|
||||||
|
|
||||||
|
// Tests that chrome.manifest remains registered at app shutdown.
|
||||||
|
checkActive(true);
|
||||||
|
});
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Test that side-loaded extensions with invalid install.rdf files are
|
||||||
|
// not initialized at startup.
|
||||||
|
|
||||||
|
const APP_ID = "xpcshell@tests.mozilla.org";
|
||||||
|
|
||||||
|
Services.prefs.setIntPref("extensions.enabledScopes", AddonManager.SCOPE_USER);
|
||||||
|
|
||||||
|
createAppInfo(APP_ID, "XPCShell", "1", "1.9.2");
|
||||||
|
|
||||||
|
const userAppDir = AddonTestUtils.profileDir.clone();
|
||||||
|
userAppDir.append("app-extensions");
|
||||||
|
userAppDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||||
|
AddonTestUtils.registerDirectory("XREUSysExt", userAppDir);
|
||||||
|
|
||||||
|
const userExtensions = userAppDir.clone();
|
||||||
|
userExtensions.append(APP_ID);
|
||||||
|
userExtensions.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyServiceGetters(this, {
|
||||||
|
ChromeRegistry: [
|
||||||
|
"@mozilla.org/chrome/chrome-registry;1",
|
||||||
|
"nsIChromeRegistry",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
function hasChromeEntry(package) {
|
||||||
|
try {
|
||||||
|
void ChromeRegistry.convertChromeURL(
|
||||||
|
Services.io.newURI(`chrome://${package}/content/`)
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function() {
|
||||||
|
await promiseWriteInstallRDFToXPI(
|
||||||
|
{
|
||||||
|
id: "langpack-foo@addons.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
type: 8,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Invalid install.rdf extension",
|
||||||
|
},
|
||||||
|
userExtensions,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
"chrome.manifest": `
|
||||||
|
content foo-langpack ./
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await promiseWriteInstallRDFToXPI(
|
||||||
|
{
|
||||||
|
id: "foo@addons.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Invalid install.rdf extension",
|
||||||
|
},
|
||||||
|
userExtensions,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
"chrome.manifest": `
|
||||||
|
content foo ./
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await promiseWriteInstallRDFToXPI(
|
||||||
|
{
|
||||||
|
id: "foo-legacy-legacy@addons.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: false,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Invalid install.rdf extension",
|
||||||
|
},
|
||||||
|
userExtensions,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
"chrome.manifest": `
|
||||||
|
content foo-legacy-legacy ./
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
equal(
|
||||||
|
hasChromeEntry("foo-langpack"),
|
||||||
|
false,
|
||||||
|
"Should not have registered foo-langpack resource before AOM startup"
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
hasChromeEntry("foo-legacy-legacy"),
|
||||||
|
false,
|
||||||
|
"Should not have registered foo-legacy-legacy resource before AOM startup"
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
hasChromeEntry("foo"),
|
||||||
|
false,
|
||||||
|
"Should not have registered foo resource before AOM startup"
|
||||||
|
);
|
||||||
|
|
||||||
|
await promiseStartupManager();
|
||||||
|
|
||||||
|
equal(
|
||||||
|
hasChromeEntry("foo-langpack"),
|
||||||
|
false,
|
||||||
|
"Should not have registered chrome manifest for invalid extension"
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
hasChromeEntry("foo-legacy-legacy"),
|
||||||
|
false,
|
||||||
|
"Should not have registered chrome manifest for non-restartless extension"
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
hasChromeEntry("foo"),
|
||||||
|
true,
|
||||||
|
"Should have registered chrome manifest for valid extension"
|
||||||
|
);
|
||||||
|
|
||||||
|
await promiseRestartManager();
|
||||||
|
|
||||||
|
equal(
|
||||||
|
hasChromeEntry("foo-langpack"),
|
||||||
|
false,
|
||||||
|
"Should still not have registered chrome manifest for invalid extension after restart"
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
hasChromeEntry("foo-legacy-legacy"),
|
||||||
|
false,
|
||||||
|
"Should still not have registered chrome manifest for non-restartless extension"
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
hasChromeEntry("foo"),
|
||||||
|
true,
|
||||||
|
"Should still have registered chrome manifest for valid extension after restart"
|
||||||
|
);
|
||||||
|
|
||||||
|
await promiseShutdownManager();
|
||||||
|
|
||||||
|
userAppDir.remove(true);
|
||||||
|
});
|
||||||
@@ -0,0 +1,806 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// This tests that all properties are read from the install manifests and that
|
||||||
|
// items are correctly enabled/disabled based on them (blocklist tests are
|
||||||
|
// elsewhere)
|
||||||
|
|
||||||
|
const ADDONS = [
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon1@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
aboutURL: "chrome://test/content/about.xul",
|
||||||
|
iconURL: "chrome://test/skin/icon.png",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 1",
|
||||||
|
description: "Test Description",
|
||||||
|
creator: "Test Creator",
|
||||||
|
homepageURL: "http://www.example.com",
|
||||||
|
developer: ["Test Developer 1", "Test Developer 2"],
|
||||||
|
translator: ["Test Translator 1", "Test Translator 2"],
|
||||||
|
contributor: ["Test Contributor 1", "Test Contributor 2"],
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
id: "addon1@tests.mozilla.org",
|
||||||
|
type: "extension",
|
||||||
|
version: "1.0",
|
||||||
|
optionsType: null,
|
||||||
|
aboutURL: "chrome://test/content/about.xul",
|
||||||
|
iconURL: "chrome://test/skin/icon.png",
|
||||||
|
icons: {
|
||||||
|
32: "chrome://test/skin/icon.png",
|
||||||
|
48: "chrome://test/skin/icon.png",
|
||||||
|
},
|
||||||
|
name: "Test Addon 1",
|
||||||
|
description: "Test Description",
|
||||||
|
creator: "Test Creator",
|
||||||
|
homepageURL: "http://www.example.com",
|
||||||
|
developers: ["Test Developer 1", "Test Developer 2"],
|
||||||
|
translators: ["Test Translator 1", "Test Translator 2"],
|
||||||
|
contributors: ["Test Contributor 1", "Test Contributor 2"],
|
||||||
|
isActive: true,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: false,
|
||||||
|
isCompatible: true,
|
||||||
|
providesUpdatesSecurely: true,
|
||||||
|
blocklistState: Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon2@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
updateURL: "https://www.foo.com",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 2",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
id: "addon2@tests.mozilla.org",
|
||||||
|
isActive: true,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: false,
|
||||||
|
providesUpdatesSecurely: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon3@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
updateURL: "http://www.foo.com",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 3",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
id: "addon3@tests.mozilla.org",
|
||||||
|
isActive: false,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: true,
|
||||||
|
providesUpdatesSecurely: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon4@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
updateURL: "http://www.foo.com",
|
||||||
|
updateKey: "foo",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 4",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
id: "addon4@tests.mozilla.org",
|
||||||
|
isActive: false,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: true,
|
||||||
|
providesUpdatesSecurely: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon5@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "*",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 5",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: true,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: false,
|
||||||
|
isCompatible: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon6@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "0",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 6",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: true,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: false,
|
||||||
|
isCompatible: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon7@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "0",
|
||||||
|
maxVersion: "0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 7",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: false,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: true,
|
||||||
|
isCompatible: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon8@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1.1",
|
||||||
|
maxVersion: "*",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 8",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: false,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: true,
|
||||||
|
isCompatible: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon9@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "toolkit@mozilla.org",
|
||||||
|
minVersion: "1.9.2",
|
||||||
|
maxVersion: "1.9.*",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 9",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: true,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: false,
|
||||||
|
isCompatible: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon10@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "toolkit@mozilla.org",
|
||||||
|
minVersion: "1.9.2.1",
|
||||||
|
maxVersion: "1.9.*",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 10",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: false,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: true,
|
||||||
|
isCompatible: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon11@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "toolkit@mozilla.org",
|
||||||
|
minVersion: "1.9",
|
||||||
|
maxVersion: "1.9.2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 11",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: true,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: false,
|
||||||
|
isCompatible: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon12@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "toolkit@mozilla.org",
|
||||||
|
minVersion: "1.9",
|
||||||
|
maxVersion: "1.9.1.*",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 12",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: false,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: true,
|
||||||
|
isCompatible: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon13@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "toolkit@mozilla.org",
|
||||||
|
minVersion: "1.9",
|
||||||
|
maxVersion: "1.9.*",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "0",
|
||||||
|
maxVersion: "0.5",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 13",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: false,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: true,
|
||||||
|
isCompatible: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon14@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "toolkit@mozilla.org",
|
||||||
|
minVersion: "1.9",
|
||||||
|
maxVersion: "1.9.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 14",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: true,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: false,
|
||||||
|
isCompatible: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon15@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
updateKey: "foo",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 15",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: true,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: false,
|
||||||
|
isCompatible: true,
|
||||||
|
providesUpdatesSecurely: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon16@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
updateKey: "foo",
|
||||||
|
updateURL: "https://www.foo.com",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 16",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: true,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: false,
|
||||||
|
isCompatible: true,
|
||||||
|
providesUpdatesSecurely: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon17@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
optionsURL: "chrome://test/content/options.xul",
|
||||||
|
optionsType: "2",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 17",
|
||||||
|
},
|
||||||
|
|
||||||
|
// An obsolete optionsType means the add-on isn't registered.
|
||||||
|
expected: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon18@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 18",
|
||||||
|
},
|
||||||
|
extraFiles: { "options.xul": "" },
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: true,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: false,
|
||||||
|
isCompatible: true,
|
||||||
|
optionsURL: null,
|
||||||
|
optionsType: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon19@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
optionsType: "99",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 19",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon20@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
optionsURL: "chrome://test/content/options.xul",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 20",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Even with a defined optionsURL optionsType is null by default.
|
||||||
|
expected: {
|
||||||
|
isActive: true,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: false,
|
||||||
|
isCompatible: true,
|
||||||
|
optionsURL: "chrome://test/content/options.xul",
|
||||||
|
optionsType: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon21@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
optionsType: "3",
|
||||||
|
optionsURL: "chrome://test/content/options.xul",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 21",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
isActive: true,
|
||||||
|
userDisabled: false,
|
||||||
|
appDisabled: false,
|
||||||
|
isCompatible: true,
|
||||||
|
optionsURL: "chrome://test/content/options.xul",
|
||||||
|
optionsType: AddonManager.OPTIONS_TYPE_TAB,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon22@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
optionsType: "2",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 22",
|
||||||
|
},
|
||||||
|
|
||||||
|
// An obsolete optionsType means the add-on isn't registered.
|
||||||
|
expected: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon23@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
optionsType: "2",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 23",
|
||||||
|
},
|
||||||
|
extraFiles: { "options.xul": "" },
|
||||||
|
|
||||||
|
// An obsolete optionsType means the add-on isn't registered.
|
||||||
|
expected: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon24@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 24",
|
||||||
|
},
|
||||||
|
extraFiles: { "options.xul": "" },
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
optionsType: null,
|
||||||
|
optionsURL: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon25@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
optionsType: "3",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 25",
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
optionsType: null,
|
||||||
|
optionsURL: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "addon26@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
optionsType: "4",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "Test Addon 26",
|
||||||
|
},
|
||||||
|
extraFiles: { "options.xul": "" },
|
||||||
|
expected: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tests compatibility based on target platforms.
|
||||||
|
|
||||||
|
// No targetPlatforms so should be compatible
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "tp-addon1@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
name: "Test 1",
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
appDisabled: false,
|
||||||
|
isPlatformCompatible: true,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Matches the OS
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "tp-addon2@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
name: "Test 2",
|
||||||
|
targetPlatforms: ["XPCShell", "WINNT_x86", "XPCShell"],
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
appDisabled: false,
|
||||||
|
isPlatformCompatible: true,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Matches the OS and ABI
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "tp-addon3@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
name: "Test 3",
|
||||||
|
targetPlatforms: ["WINNT", "XPCShell_noarch-spidermonkey"],
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
appDisabled: false,
|
||||||
|
isPlatformCompatible: true,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Doesn't match
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "tp-addon4@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
name: "Test 4",
|
||||||
|
targetPlatforms: [
|
||||||
|
"WINNT_noarch-spidermonkey",
|
||||||
|
"Darwin",
|
||||||
|
"WINNT_noarch-spidermonkey",
|
||||||
|
],
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
appDisabled: true,
|
||||||
|
isPlatformCompatible: false,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Matches the OS but since a different entry specifies ABI this doesn't match.
|
||||||
|
{
|
||||||
|
"install.rdf": {
|
||||||
|
id: "tp-addon5@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
bootstrap: true,
|
||||||
|
name: "Test 5",
|
||||||
|
targetPlatforms: ["XPCShell", "XPCShell_foo"],
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: {
|
||||||
|
appDisabled: true,
|
||||||
|
isPlatformCompatible: false,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const IDS = ADDONS.map(a => a["install.rdf"].id);
|
||||||
|
|
||||||
|
add_task(async function setup() {
|
||||||
|
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
|
||||||
|
const profileDir = gProfD.clone();
|
||||||
|
profileDir.append("extensions");
|
||||||
|
|
||||||
|
for (let addon of ADDONS) {
|
||||||
|
await promiseWriteInstallRDFForExtension(
|
||||||
|
addon["install.rdf"],
|
||||||
|
profileDir,
|
||||||
|
undefined,
|
||||||
|
addon.extraFiles
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_values() {
|
||||||
|
await promiseStartupManager();
|
||||||
|
|
||||||
|
let addons = await getAddons(IDS);
|
||||||
|
|
||||||
|
for (let addon of ADDONS) {
|
||||||
|
let { id } = addon["install.rdf"];
|
||||||
|
checkAddon(id, addons.get(id), addon.expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
await promiseShutdownManager();
|
||||||
|
});
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const ID = "bug397778@tests.mozilla.org";
|
||||||
|
|
||||||
|
const ADDON = createInstallRDF({
|
||||||
|
id: "bug397778@tests.mozilla.org",
|
||||||
|
version: "1.0",
|
||||||
|
name: "Fallback Name",
|
||||||
|
description: "Fallback Description",
|
||||||
|
bootstrap: true,
|
||||||
|
|
||||||
|
targetApplications: [
|
||||||
|
{
|
||||||
|
id: "xpcshell@tests.mozilla.org",
|
||||||
|
minVersion: "1",
|
||||||
|
maxVersion: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
localized: [
|
||||||
|
{
|
||||||
|
locale: ["fr"],
|
||||||
|
name: "fr Name",
|
||||||
|
description: "fr Description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: ["de-DE"],
|
||||||
|
name: "Deutsches W\u00f6rterbuch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: ["es-ES"],
|
||||||
|
name: "es-ES Name",
|
||||||
|
description: "es-ES Description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: ["zh-TW"],
|
||||||
|
name: "zh-TW Name",
|
||||||
|
description: "zh-TW Description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: ["zh-CN"],
|
||||||
|
name: "zh-CN Name",
|
||||||
|
description: "zh-CN Description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: ["en-GB"],
|
||||||
|
name: "en-GB Name",
|
||||||
|
description: "en-GB Description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: ["en"],
|
||||||
|
name: "en Name",
|
||||||
|
description: "en Description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
locale: ["en-CA"],
|
||||||
|
name: "en-CA Name",
|
||||||
|
description: "en-CA Description",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function setup() {
|
||||||
|
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1");
|
||||||
|
Services.locale.requestedLocales = ["fr-FR"];
|
||||||
|
|
||||||
|
await promiseStartupManager();
|
||||||
|
await promiseInstallXPI(ADDON);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_1() {
|
||||||
|
let addon = await AddonManager.getAddonByID(ID);
|
||||||
|
Assert.notEqual(addon, null);
|
||||||
|
Assert.equal(addon.name, "fr Name");
|
||||||
|
Assert.equal(addon.description, "fr Description");
|
||||||
|
|
||||||
|
await addon.disable();
|
||||||
|
await promiseRestartManager();
|
||||||
|
|
||||||
|
let newAddon = await AddonManager.getAddonByID(ID);
|
||||||
|
Assert.notEqual(newAddon, null);
|
||||||
|
Assert.equal(newAddon.name, "fr Name");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_2() {
|
||||||
|
// Change locale. The more specific de-DE is the best match
|
||||||
|
await restartWithLocales(["de"]);
|
||||||
|
|
||||||
|
let addon = await AddonManager.getAddonByID(ID);
|
||||||
|
Assert.notEqual(addon, null);
|
||||||
|
Assert.equal(addon.name, "Deutsches W\u00f6rterbuch");
|
||||||
|
Assert.equal(addon.description, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_3() {
|
||||||
|
// Change locale. Locale case should have no effect
|
||||||
|
await restartWithLocales(["DE-de"]);
|
||||||
|
|
||||||
|
let addon = await AddonManager.getAddonByID(ID);
|
||||||
|
Assert.notEqual(addon, null);
|
||||||
|
Assert.equal(addon.name, "Deutsches W\u00f6rterbuch");
|
||||||
|
Assert.equal(addon.description, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_4() {
|
||||||
|
// Change locale. es-ES should closely match
|
||||||
|
await restartWithLocales(["es-AR"]);
|
||||||
|
|
||||||
|
let addon = await AddonManager.getAddonByID(ID);
|
||||||
|
Assert.notEqual(addon, null);
|
||||||
|
Assert.equal(addon.name, "es-ES Name");
|
||||||
|
Assert.equal(addon.description, "es-ES Description");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_5() {
|
||||||
|
// Change locale. Either zh-CN or zh-TW could match
|
||||||
|
await restartWithLocales(["zh"]);
|
||||||
|
|
||||||
|
let addon = await AddonManager.getAddonByID(ID);
|
||||||
|
Assert.notEqual(addon, null);
|
||||||
|
ok(
|
||||||
|
addon.name == "zh-TW Name" || addon.name == "zh-CN Name",
|
||||||
|
`Add-on name mismatch: ${addon.name}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_6() {
|
||||||
|
// Unknown locale should try to match against en-US as well. Of en,en-GB
|
||||||
|
// en should match as being less specific
|
||||||
|
await restartWithLocales(["nl-NL"]);
|
||||||
|
|
||||||
|
let addon = await AddonManager.getAddonByID(ID);
|
||||||
|
Assert.notEqual(addon, null);
|
||||||
|
Assert.equal(addon.name, "en Name");
|
||||||
|
Assert.equal(addon.description, "en Description");
|
||||||
|
});
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
tags = addons
|
||||||
|
head = head_addons.js
|
||||||
|
support-files =
|
||||||
|
data/**
|
||||||
|
|
||||||
|
[test_bootstrap.js]
|
||||||
|
[test_bootstrap_const.js]
|
||||||
|
[test_bootstrap_globals.js]
|
||||||
|
[test_bootstrapped_chrome_manifest.js]
|
||||||
|
[test_invalid_install_rdf.js]
|
||||||
|
[test_manifest.js]
|
||||||
|
[test_manifest_locales.js]
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
DIRS += ["extensions/common"]
|
||||||
|
|
||||||
DIST_SUBDIR = "browser"
|
DIST_SUBDIR = "browser"
|
||||||
export("DIST_SUBDIR")
|
export("DIST_SUBDIR")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user