Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug 923897 - Extensibility support for b2g #7

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions b2g/app/b2g.js
Original file line number Diff line number Diff line change
Expand Up @@ -888,3 +888,6 @@ pref("browser.autofocus", false);

// Enable wakelock
pref("dom.wakelock.enabled", true);

// Enable webapps add-ons
pref("dom.apps.customization.enabled", true);
5 changes: 5 additions & 0 deletions dom/apps/src/AppsUtils.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,11 @@ this.AppsUtils = {

// Convert the binary hash data to a hex string.
return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
},

// Returns the hash for a JS object.
computeObjectHash: function(aObject) {
return this.computeHash(JSON.stringify(aObject));
}
}

Expand Down
244 changes: 244 additions & 0 deletions dom/apps/src/UserCustomization.jsm
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
/* 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/. */

/* XXX TODO
* unregister as much as possible when removing a customization.
*/

"use strict";

const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;

this.EXPORTED_SYMBOLS = ["UserCustomization"];

Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppsUtils.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:
* "customization": [
* {
* "filter": "http://youtube.com",
* "css": ["file1.css", "file2.css"],
* "scripts": ["script1.js", "script2.js"]
* }
* ]
*/

function debug(aStr) {
dump("-*-*- UserCustomization (" +
(UserCustomization._inParent ? "parent" : "child") +
"): " + aStr + "\n");
}

function log(aStr) {
console.logStringMessage(aStr);
}

this.UserCustomization = {
_items: [],

_addItem: function(aItem) {
debug("Registering item: " + uneval(aItem));
this._items.push(aItem);
if (this._inParent) {
ppmm.broadcastAsyncMessage("UserCustomization:Add", [aItem]);
}
},

_removeItem: function(aHash) {
debug("Unregistering item: " + 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("UserCustomization:Remove", aHash);
}
},

register: function(aManifest, aApp) {
let enabled = false;
try {
enabled = Services.prefs.getBoolPref("dom.apps.customization.enabled");
} catch(e) {}
if (!enabled) {
return;
}

debug("Starting customization registration for " + aApp.origin);
let customization = aManifest.customization;
if (customization === undefined || !Array.isArray(customization)) {
return;
}

let origin = Services.io.newURI(aApp.origin, null, null);

customization.forEach((item) => {
// The filter property is mandatory.
// XXX do a format check? should this be regexp?
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,
css: [],
scripts: []
};
custom.hash = AppsUtils.computeObjectHash(item);

if (item.css && Array.isArray(item.css)) {
item.css.forEach((css) => {
custom.css.push(origin.resolve(css));
});
}

if (item.scripts && Array.isArray(item.scripts)) {
item.scripts.forEach((script) => {
custom.scripts.push(origin.resolve(script));
});
}

this._addItem(custom);
});
},

unregister: function(aManifest, aApp) {
debug("Starting customization unregistration for " + aApp.origin);
let customization = aManifest.customization;
if (customization === undefined || !Array.isArray(customization)) {
return;
}

let origin = Services.io.newURI(aApp.origin, null, null);

customization.forEach((item) => {
this._removeItem(AppsUtils.computeObjectHash(item));
});
},

_injectItem: function(aWindow, aItem) {
debug("Injecting item " + uneval(aItem) + " in " + aWindow.location.href);
let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);

// Load the stylesheets only in this window.
aItem.css.forEach((aCss) => {
utils.loadSheet(Services.io.newURI(aCss, null, null),
Ci.nsIDOMWindowUtils.AUTHOR_SHEET);
});

let sandbox = Cu.Sandbox(aWindow,
{ wantComponents: false,
wantXrays: true,
sandboxPrototype: aWindow });

// Load the scripts using a sandbox.
aItem.scripts.forEach((aScript) => {
debug("Sandboxing " + aScript);
try {
Services.scriptloader.loadSubScript(aScript, sandbox, "UTF-8");
} catch(e) {
log("Error sandboxing " + aScript + " : " + e);
}
});

// Makes sure we get rid of the sandbox.
aWindow.addEventListener("unload", () => {
Cu.nukeSandbox(sandbox);
sandbox = null;
});
},

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 principal = window.document.nodePrincipal;
debug("document created: " + href);
debug("principal status: " + principal.appStatus);

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;
}

if (href.startsWith(aItem.filter)) {
this._injectItem(window, aItem);
}
});
}
},

init: function() {
debug("init");
this._inParent = Cc["@mozilla.org/xre/runtime;1"]
.getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;

Services.obs.addObserver(this, "content-document-global-created",
/* ownsWeak */ false);

if (this._inParent) {
ppmm.addMessageListener("UserCustomization:List", this);
} else {
cpmm.addMessageListener("UserCustomization:Add", this);
cpmm.addMessageListener("UserCustomization:Remove", this);
cpmm.sendAsyncMessage("UserCustomization:List", {});
}
},

receiveMessage: function(aMessage) {
let name = aMessage.name;
let data = aMessage.data;

switch(name) {
case "UserCustomization:List":
aMessage.target.sendAsyncMessage("UserCustomization:Add", this._items);
break;
case "UserCustomization:Add":
data.forEach(this._addItem, this);
break;
case "UserCustomization:Remove":
this._removeItem(data);
break;
}
}
}

UserCustomization.init();
6 changes: 6 additions & 0 deletions dom/apps/src/Webapps.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Cu.import("resource://gre/modules/WebappOSUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/UserCustomization.jsm");

#ifdef MOZ_WIDGET_GONK
XPCOMUtils.defineLazyGetter(this, "libcutils", function() {
Expand Down Expand Up @@ -311,6 +312,7 @@ this.DOMApplicationRegistry = {
if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
app.redirects = this.sanitizeRedirects(aResult.redirects);
}
UserCustomization.register(aResult.manifest, app);
});
});

Expand Down Expand Up @@ -924,6 +926,7 @@ this.DOMApplicationRegistry = {
this._registerSystemMessages(manifest, app);
this._registerInterAppConnections(manifest, app);
appsToRegister.push({ manifest: manifest, app: app });
UserCustomization.register(manifest, app);
});
this._registerActivitiesForApps(appsToRegister, aRunUpdate);
});
Expand Down Expand Up @@ -1580,10 +1583,12 @@ this.DOMApplicationRegistry = {
if (supportSystemMessages()) {
if (aOldManifest) {
this._unregisterActivities(aOldManifest, aApp);
UserCustomization.unregister(aOldManifest, aApp);
}
this._registerSystemMessages(aNewManifest, aApp);
this._registerActivities(aNewManifest, aApp, true);
this._registerInterAppConnections(aNewManifest, aApp);
UserCustomization.register(aNewManifest, app);
} else {
// Nothing else to do but notifying we're ready.
this.notifyAppsRegistryReady();
Expand Down Expand Up @@ -3401,6 +3406,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
if (supportSystemMessages()) {
this._readManifests([{ id: id }]).then((aResult) => {
this._unregisterActivities(aResult[0].manifest, app);
UserCustomization.unregister(aResult[0].manifest, app);
});
}

Expand Down
1 change: 1 addition & 0 deletions dom/apps/src/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ EXTRA_JS_MODULES += [
'OfflineCacheInstaller.jsm',
'PermissionsInstaller.jsm',
'PermissionsTable.jsm',
'UserCustomization.jsm',
]

EXTRA_PP_JS_MODULES += [
Expand Down
1 change: 1 addition & 0 deletions dom/ipc/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const BrowserElementIsPreloaded = true;
Cu.import("resource://gre/modules/SettingsDB.jsm");
Cu.import("resource://gre/modules/SettingsQueue.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/UserCustomization.jsm")

Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci["nsIAppShellService"]);
Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci["nsIWindowMediator"]);
Expand Down
2 changes: 1 addition & 1 deletion js/xpconnect/loader/mozJSSubScriptLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ mozJSSubScriptLoader::DoLoadSubScriptWithOptions(const nsAString &url,
return ReportError(cx, LOAD_ERROR_NOSCHEME);
}

if (!scheme.EqualsLiteral("chrome")) {
if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("app")) {
// This might be a URI to a local file, though!
nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(innerURI);
Expand Down