Bug 1190995 - Support the new extension model in b2g r=billm

This commit is contained in:
Fabrice Desré 2015-08-14 16:55:09 -07:00
parent 36b2972b20
commit 6c5bfd7cca
29 changed files with 124 additions and 368 deletions

View File

@ -1174,4 +1174,3 @@ pref("dom.presentation.device.name", "Firefox OS");
// Enable notification of performance timing
pref("dom.performance.enable_notify_performance_timing", true);

View File

@ -165,9 +165,7 @@ ProcessGlobal.prototype = {
.getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
if (inParent) {
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
ppmm.addMessageListener("getProfD", function(message) {
Services.ppmm.addMessageListener("getProfD", function(message) {
return Services.dirsvc.get("ProfD", Ci.nsIFile).path;
});

View File

@ -1006,3 +1006,6 @@ bin/libfreebl_32int64_3.so
#ifdef PKG_LOCALE_MANIFEST
#include @PKG_LOCALE_MANIFEST@
#endif
@RESPATH@/components/simpleServices.js
@RESPATH@/components/utils.manifest

View File

@ -12,38 +12,18 @@ this.EXPORTED_SYMBOLS = ["UserCustomizations"];
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import("resource://gre/modules/Extension.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
XPCOMUtils.defineLazyServiceGetter(this, "console",
"@mozilla.org/consoleservice;1",
"nsIConsoleService");
/**
* Customization scripts and CSS stylesheets can be specified in an
* application manifest with the following syntax:
* "customizations": [
* {
* "filter": "http://youtube.com",
* "css": ["file1.css", "file2.css"],
* "scripts": ["script1.js", "script2.js"]
* }
* ]
*/
function debug(aMsg) {
if (!UserCustomizations._debug) {
return;
}
dump("-*-*- UserCustomizations (" +
(UserCustomizations._inParent ? "parent" : "child") +
"): " + aMsg + "\n");
dump("-*-*- UserCustomizations " + aMsg + "\n");
}
function log(aStr) {
@ -51,267 +31,36 @@ function log(aStr) {
}
this.UserCustomizations = {
_debug: false,
_items: [],
_loaded : {}, // Keep track per manifestURL of css and scripts loaded.
_windows: null, // Set of currently opened windows.
_enabled: false,
_addItem: function(aItem) {
debug("_addItem: " + uneval(aItem));
this._items.push(aItem);
if (this._inParent) {
ppmm.broadcastAsyncMessage("UserCustomizations:Add", [aItem]);
}
},
_removeItem: function(aHash) {
debug("_removeItem: " + aHash);
let index = -1;
this._items.forEach((script, pos) => {
if (script.hash == aHash ) {
index = pos;
}
});
if (index != -1) {
this._items.splice(index, 1);
}
if (this._inParent) {
ppmm.broadcastAsyncMessage("UserCustomizations:Remove", aHash);
}
},
register: function(aManifest, aApp) {
debug("Starting customization registration for " + aApp.manifestURL);
extensions: new Map(), // id -> extension. Needed to disable extensions.
register: function(aApp) {
if (!this._enabled || !aApp.enabled || aApp.role != "addon") {
debug("Rejecting registration (global enabled=" + this._enabled +
") (app role=" + aApp.role +
", enabled=" + aApp.enabled + ")");
debug(uneval(aApp));
return;
}
let customizations = aManifest.customizations;
if (customizations === undefined || !Array.isArray(customizations)) {
return;
}
debug("Starting customization registration for " + aApp.manifestURL + "\n");
let base = Services.io.newURI(aApp.origin, null, null);
customizations.forEach(item => {
// The filter property is mandatory.
if (!item.filter || (typeof item.filter !== "string")) {
log("Mandatory filter property not found in this customization item: " +
uneval(item) + " in " + aApp.manifestURL);
return;
}
// Create a new object with resolved urls and a hash that we reuse to
// remove items.
let custom = {
filter: item.filter,
status: aApp.appStatus,
manifestURL: aApp.manifestURL,
css: [],
scripts: []
};
custom.hash = AppsUtils.computeObjectHash(item);
if (item.css && Array.isArray(item.css)) {
item.css.forEach((css) => {
custom.css.push(base.resolve(css));
});
}
if (item.scripts && Array.isArray(item.scripts)) {
item.scripts.forEach((script) => {
custom.scripts.push(base.resolve(script));
});
}
this._addItem(custom);
let extension = new Extension({
id: aApp.manifestURL,
resourceURI: Services.io.newURI(aApp.origin + "/", null, null)
});
this._updateAllWindows();
this.extensions.set(aApp.manifestURL, extension);
extension.startup();
},
_updateAllWindows: function() {
debug("UpdateWindows");
if (this._inParent) {
ppmm.broadcastAsyncMessage("UserCustomizations:UpdateWindows", {});
}
// Inject in all currently opened windows.
this._windows.forEach(this._injectInWindow.bind(this));
},
unregister: function(aManifest, aApp) {
unregister: function(aApp) {
if (!this._enabled) {
return;
}
debug("Starting customization unregistration for " + aApp.manifestURL);
let customizations = aManifest.customizations;
if (customizations === undefined || !Array.isArray(customizations)) {
return;
}
customizations.forEach(item => {
this._removeItem(AppsUtils.computeObjectHash(item));
});
this._unloadForManifestURL(aApp.manifestURL);
},
_unloadForManifestURL: function(aManifestURL) {
debug("_unloadForManifestURL " + aManifestURL);
if (this._inParent) {
ppmm.broadcastAsyncMessage("UserCustomizations:Unload", aManifestURL);
}
if (!this._loaded[aManifestURL]) {
return;
}
if (this._loaded[aManifestURL].scripts &&
this._loaded[aManifestURL].scripts.length > 0) {
// We can't rollback script changes, so don't even try to unload in this
// situation.
return;
}
this._loaded[aManifestURL].css.forEach(aItem => {
try {
debug("unloading " + aItem.uri.spec);
let utils = aItem.window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
utils.removeSheet(aItem.uri, Ci.nsIDOMWindowUtils.AUTHOR_SHEET);
} catch(e) {
log("Error unloading stylesheet " + aItem.uri.spec + " : " + e);
}
});
this._loaded[aManifestURL] = null;
},
_injectItem: function(aWindow, aItem, aInjected) {
debug("Injecting item " + uneval(aItem) + " in " + aWindow.location.href);
let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let manifestURL = aItem.manifestURL;
// Load the stylesheets only in this window.
aItem.css.forEach(aCss => {
if (aInjected.indexOf(aCss) !== -1) {
debug("Skipping duplicated css: " + aCss);
return;
}
let uri = Services.io.newURI(aCss, null, null);
try {
utils.loadSheet(uri, Ci.nsIDOMWindowUtils.AUTHOR_SHEET);
if (!this._loaded[manifestURL]) {
this._loaded[manifestURL] = { css: [], scripts: [] };
}
this._loaded[manifestURL].css.push({ window: aWindow, uri: uri });
aInjected.push(aCss);
} catch(e) {
log("Error loading stylesheet " + aCss + " : " + e);
}
});
let sandbox;
if (aItem.scripts.length > 0) {
sandbox = Cu.Sandbox([aWindow],
{ wantComponents: false,
sandboxPrototype: aWindow });
}
// Load the scripts using a sandbox.
aItem.scripts.forEach(aScript => {
debug("Sandboxing " + aScript);
if (aInjected.indexOf(aScript) !== -1) {
debug("Skipping duplicated script: " + aScript);
return;
}
try {
let options = {
target: sandbox,
charset: "UTF-8",
async: true
}
Services.scriptloader.loadSubScriptWithOptions(aScript, options);
if (!this._loaded[manifestURL]) {
this._loaded[manifestURL] = { css: [], scripts: [] };
}
this._loaded[manifestURL].scripts.push({ sandbox: sandbox, uri: aScript });
aInjected.push(aScript);
} catch(e) {
log("Error sandboxing " + aScript + " : " + e);
}
});
// Makes sure we get rid of the sandbox.
if (sandbox) {
aWindow.addEventListener("unload", () => {
Cu.nukeSandbox(sandbox);
sandbox = null;
});
}
},
_injectInWindow: function(aWindow) {
debug("_injectInWindow");
if (!aWindow || !aWindow.document) {
return;
}
let principal = aWindow.document.nodePrincipal;
debug("principal status: " + principal.appStatus);
let href = aWindow.location.href;
// The list of resources loaded in this window, used to filter out
// duplicates.
let injected = [];
this._items.forEach((aItem) => {
// We only allow customizations to apply to apps with an equal or lower
// privilege level.
if (principal.appStatus > aItem.status) {
return;
}
let regexp = new RegExp(aItem.filter, "g");
if (regexp.test(href)) {
this._injectItem(aWindow, aItem, injected);
debug("Currently injected: " + injected.toString());
}
});
},
observe: function(aSubject, aTopic, aData) {
if (aTopic === "content-document-global-created") {
let window = aSubject.QueryInterface(Ci.nsIDOMWindow);
let href = window.location.href;
if (!href || href == "about:blank") {
return;
}
let id = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.currentInnerWindowID;
this._windows.set(id, window);
debug("document created: " + href);
this._injectInWindow(window);
} else if (aTopic === "inner-window-destroyed") {
let winId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
this._windows.delete(winId);
if (this.extensions.has(aApp.manifestURL)) {
this.extensions.get(aApp.manifestURL).shutdown();
this.extensions.delete(aApp.manifestURL);
}
},
@ -320,56 +69,7 @@ this.UserCustomizations = {
try {
this._enabled = Services.prefs.getBoolPref("dom.apps.customization.enabled");
} catch(e) {}
if (!this._enabled) {
return;
}
this._windows = new Map(); // Can't be a WeakMap because we need to enumerate.
this._inParent = Cc["@mozilla.org/xre/runtime;1"]
.getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
debug("init");
Services.obs.addObserver(this, "content-document-global-created",
/* ownsWeak */ false);
Services.obs.addObserver(this, "inner-window-destroyed",
/* ownsWeak */ false);
if (this._inParent) {
ppmm.addMessageListener("UserCustomizations:List", this);
} else {
cpmm.addMessageListener("UserCustomizations:Add", this);
cpmm.addMessageListener("UserCustomizations:Remove", this);
cpmm.addMessageListener("UserCustomizations:Unload", this);
cpmm.addMessageListener("UserCustomizations:UpdateWindows", this);
cpmm.sendAsyncMessage("UserCustomizations:List", {});
}
},
receiveMessage: function(aMessage) {
let name = aMessage.name;
let data = aMessage.data;
switch(name) {
case "UserCustomizations:List":
aMessage.target.sendAsyncMessage("UserCustomizations:Add", this._items);
break;
case "UserCustomizations:Add":
data.forEach(this._addItem, this);
break;
case "UserCustomizations:Remove":
this._removeItem(data);
break;
case "UserCustomizations:Unload":
this._unloadForManifestURL(data);
break;
case "UserCustomizations:UpdateWindows":
this._updateAllWindows();
break;
}
}
}
UserCustomizations.init();

View File

@ -439,7 +439,7 @@ this.DOMApplicationRegistry = {
app.redirects = this.sanitizeRedirects(aResult.redirects);
}
app.kind = this.appKind(app, aResult.manifest);
UserCustomizations.register(aResult.manifest, app);
UserCustomizations.register(app);
Langpacks.register(app, aResult.manifest);
});
@ -1164,7 +1164,7 @@ this.DOMApplicationRegistry = {
this._registerSystemMessages(manifest, app);
this._registerInterAppConnections(manifest, app);
appsToRegister.push({ manifest: manifest, app: app });
UserCustomizations.register(manifest, app);
UserCustomizations.register(app);
Langpacks.register(app, manifest);
});
this._safeToClone.resolve();
@ -2013,10 +2013,10 @@ this.DOMApplicationRegistry = {
// Update user customizations and langpacks.
if (aOldManifest) {
UserCustomizations.unregister(aOldManifest, aApp);
UserCustomizations.unregister(aApp);
Langpacks.unregister(aApp, aOldManifest);
}
UserCustomizations.register(aNewManifest, aApp);
UserCustomizations.register(aApp);
Langpacks.register(aApp, aNewManifest);
},
@ -4125,7 +4125,7 @@ this.DOMApplicationRegistry = {
if (supportSystemMessages()) {
this._unregisterActivities(aApp.manifest, aApp);
}
UserCustomizations.unregister(aApp.manifest, aApp);
UserCustomizations.unregister(aApp);
Langpacks.unregister(aApp, aApp.manifest);
let dir = this._getAppDir(id);
@ -4545,10 +4545,11 @@ this.DOMApplicationRegistry = {
});
// Update customization.
this.getManifestFor(app.manifestURL).then((aManifest) => {
app.enabled ? UserCustomizations.register(aManifest, app)
: UserCustomizations.unregister(aManifest, app);
});
if (app.enabled) {
UserCustomizations.register(app);
} else {
UserCustomizations.unregister(app);
}
},
getManifestFor: function(aManifestURL, aEntryPoint) {

View File

@ -10,8 +10,12 @@ function sendAlertsForNode(node) {
}
function run() {
sendAlertsForNode(document.getElementById("header"));
sendAlertsForNode(document.getElementById("header2"));
// We need to wait for the next tick because add-ons are injected in the
// onload event too.
window.setTimeout(function() {
sendAlertsForNode(document.getElementById("header"));
sendAlertsForNode(document.getElementById("header2"));
}, 0);
}
</script>
</head>

View File

@ -0,0 +1,12 @@
{
"name": "Addon app",
"version": "1.0",
"manifest_version": 2,
"permissions": ["tabs"],
"description": "Let me inject script and css!",
"content_scripts": [
{"matches": ["http://mochi.test/tests/dom/apps/tests/addons/index.html"],
"js": ["script.js", "script2.js", "invalid.js", "script.js"],
"css": ["style.css", "style2.css"]}
]
}

View File

@ -1,12 +1,5 @@
{
"name": "Addon app",
"description": "Let me inject script and css!",
"customizations" : [
{
"filter": "http://mochi.test:8888/tests/dom/apps/tests/addons",
"css": ["style.css", "style2.css", "invalid.css", "style.css"],
"scripts": ["script.js", "script2.js", "invalid.js", "script.js"]
}
],
"role": "addon"
}

View File

@ -1,4 +1,5 @@
document.addEventListener("DOMContentLoaded", function() {
var head = document.getElementById("header");
head.innerHTML = "Hello World!";
}, false);
// Simple script that changes an element's content.
var head = document.getElementById("header");
head.innerHTML = "Hello World!";

View File

@ -1,4 +1,6 @@
document.addEventListener("DOMContentLoaded", function() {
var head = document.getElementById("header2");
head.innerHTML = "Customized content";
}, false);
// Simple script that changes an element's content.
var head = document.getElementById("header2");
head.innerHTML = "Customized content";

View File

@ -1732,4 +1732,3 @@ BrowserElementChild.prototype = {
};
var api = new BrowserElementChild();

View File

@ -234,6 +234,8 @@ BrowserElementParent.prototype = {
.apply(self, arguments);
}
});
this._mm.loadFrameScript("chrome://global/content/extensions.js", true);
},
/**

15
dom/ipc/extensions.js Normal file
View File

@ -0,0 +1,15 @@
/* 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";
dump("######################## extensions.js loaded\n");
Components.utils.import("resource://gre/modules/ExtensionContent.jsm");
ExtensionContent.init(this);
addEventListener("unload", () => {
ExtensionContent.uninit(this);
});

View File

@ -10,5 +10,6 @@ toolkit.jar:
content/global/BrowserElementCopyPaste.js (../browser-element/BrowserElementCopyPaste.js)
content/global/BrowserElementPanning.js (../browser-element/BrowserElementPanning.js)
* content/global/BrowserElementPanningAPZDisabled.js (../browser-element/BrowserElementPanningAPZDisabled.js)
content/global/extensions.js (extensions.js)
content/global/manifestMessages.js (manifestMessages.js)
content/global/preload.js (preload.js)

View File

@ -27,11 +27,6 @@ const BrowserElementIsPreloaded = true;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/SettingsDB.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
try {
if (Services.prefs.getBoolPref("dom.apps.customization.enabled")) {
Cu.import("resource://gre/modules/UserCustomizations.jsm");
}
} catch(e) {}
Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci["nsIAppShellService"]);
Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci["nsIWindowMediator"]);

View File

@ -308,7 +308,8 @@ SubstitutingProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *bas
nsresult rv = baseURI->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
if (!scheme.Equals(mScheme)) {
if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")) {
if (mEnforceFileOrJar && !scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")
&& !scheme.EqualsLiteral("app")) {
NS_WARNING("Refusing to create substituting URI to non-file:// target");
return NS_ERROR_INVALID_ARG;
}

View File

@ -4,7 +4,7 @@
"use strict";
const EXPORTED_SYMBOLS = ["Extension"];
this.EXPORTED_SYMBOLS = ["Extension"];
/*
* This file is the main entry point for extensions. When an extension
@ -306,7 +306,7 @@ let GlobalManager = {
// We create one instance of this class per extension. |addonData|
// comes directly from bootstrap.js when initializing.
function Extension(addonData)
this.Extension = function(addonData)
{
let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
let uuid = uuidGenerator.generateUUID().number;

View File

@ -4,7 +4,7 @@
"use strict";
const EXPORTED_SYMBOLS = ["ExtensionContent"];
this.EXPORTED_SYMBOLS = ["ExtensionContent"];
/*
* This file handles the content process side of extensions. It mainly
@ -19,6 +19,7 @@ const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
"resource://gre/modules/ExtensionManagement.jsm");
@ -147,8 +148,20 @@ Script.prototype = {
let scheduled = this.run_at || "document_idle";
if (shouldRun(scheduled)) {
for (let url of this.js) {
// On gonk we need to load the resources asynchronously because the
// app: channels only support asyncOpen. This is safe only in the
// `document_idle` state.
if (AppConstants.platform == "gonk" && scheduled != "document_idle") {
Cu.reportError(`Script injection: ignoring ${url} at ${scheduled}`);
}
url = extension.baseURI.resolve(url);
Services.scriptloader.loadSubScript(url, sandbox);
let options = {
target: sandbox,
charset: "UTF-8",
async: AppConstants.platform == "gonk"
}
Services.scriptloader.loadSubScriptWithOptions(url, options);
}
if (this.options.jsCode) {
@ -225,6 +238,7 @@ ExtensionContext.prototype = {
callOnClose(obj) {
this.onClose.add(obj);
Cu.nukeSandbox(this.sandbox);
},
forgetOnClose(obj) {
@ -476,7 +490,7 @@ let ExtensionManager = {
}
};
let ExtensionContent = {
this.ExtensionContent = {
globals: new Map(),
init(global) {

View File

@ -4,7 +4,7 @@
"use strict";
const EXPORTED_SYMBOLS = ["ExtensionManagement"];
this.EXPORTED_SYMBOLS = ["ExtensionManagement"];
const Ci = Components.interfaces;
const Cc = Components.classes;
@ -188,7 +188,7 @@ let Service = {
},
};
let ExtensionManagement = {
this.ExtensionManagement = {
startupExtension: Service.startupExtension.bind(Service),
shutdownExtension: Service.shutdownExtension.bind(Service),
@ -198,4 +198,3 @@ let ExtensionManagement = {
getFrameId: Frames.getId.bind(Frames),
getParentFrameId: Frames.getParentId.bind(Frames),
};

View File

@ -4,7 +4,7 @@
"use strict";
const EXPORTED_SYMBOLS = ["ExtensionStorage"];
this.EXPORTED_SYMBOLS = ["ExtensionStorage"];
const Ci = Components.interfaces;
const Cc = Components.classes;
@ -19,7 +19,7 @@ Cu.import("resource://gre/modules/AsyncShutdown.jsm");
let Path = OS.Path;
let profileDir = OS.Constants.Path.profileDir;
let ExtensionStorage = {
this.ExtensionStorage = {
cache: new Map(),
listeners: new Map(),

View File

@ -4,7 +4,7 @@
"use strict";
const EXPORTED_SYMBOLS = ["ExtensionUtils"];
this.EXPORTED_SYMBOLS = ["ExtensionUtils"];
const Ci = Components.interfaces;
const Cc = Components.classes;
@ -527,7 +527,8 @@ Messenger.prototype = {
},
};
let ExtensionUtils = {
this.ExtensionUtils = {
runSafeWithoutClone,
runSafe,
DefaultWeakMap,
EventManager,

View File

@ -1,3 +1,5 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
let {
EventManager,

View File

@ -1,3 +1,5 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
let {
EventManager,

View File

@ -1,3 +1,5 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
let {
EventManager,

View File

@ -1,3 +1,5 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
"resource://gre/modules/ExtensionStorage.jsm");

View File

@ -1,3 +1,7 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
"resource://gre/modules/ExtensionManagement.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",

View File

@ -1,3 +1,7 @@
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
"resource://gre/modules/MatchPattern.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebRequest",

View File

@ -6,9 +6,9 @@
const Cu = Components.utils;
const EXPORTED_SYMBOLS = ["MatchPattern"];
this.EXPORTED_SYMBOLS = ["MatchPattern"];
const PERMITTED_SCHEMES = ["http", "https", "file", "ftp"];
const PERMITTED_SCHEMES = ["http", "https", "file", "ftp", "app"];
// This function converts a glob pattern (containing * and possibly ?
// as wildcards) to a regular expression.
@ -37,7 +37,7 @@ function SingleMatchPattern(pat)
} else if (!pat) {
this.scheme = [];
} else {
let re = new RegExp("^(http|https|file|ftp|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+|)(/.*)$");
let re = new RegExp("^(http|https|file|ftp|app|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+|)(/.*)$");
let match = re.exec(pat);
if (!match) {
Cu.reportError(`Invalid match pattern: '${pat}'`);
@ -91,7 +91,7 @@ SingleMatchPattern.prototype = {
}
};
function MatchPattern(pat)
this.MatchPattern = function(pat)
{
this.pat = pat;
if (!pat) {