gecko-dev/toolkit/mozapps/extensions/content/extensions-content.js

327 lines
10 KiB
JavaScript

/*
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Extension Manager.
#
# The Initial Developer of the Original Code is
# the Mozilla Foundation.
# Portions created by the Initial Developer are Copyright (C) 2010
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Alon Zakai <azakai@mozilla.com>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
*/
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled";
const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage";
const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
const MSG_JAR_FLUSH = "AddonJarFlush";
var gIoService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
function createInstallTrigger(window) {
let chromeObject = {
window: window,
url: window.document.documentURIObject,
__exposedProps__: {
SKIN: "r",
LOCALE: "r",
CONTENT: "r",
PACKAGE: "r",
enabled: "r",
updateEnabled: "r",
install: "r",
installChrome: "r",
startSoftwareUpdate: "r"
},
// == Public interface ==
SKIN: Ci.amIInstallTrigger.SKIN,
LOCALE: Ci.amIInstallTrigger.LOCALE,
CONTENT: Ci.amIInstallTrigger.CONTENT,
PACKAGE: Ci.amIInstallTrigger.PACKAGE,
/**
* @see amIInstallTriggerInstaller.idl
*/
enabled: function() {
return sendSyncMessage(MSG_INSTALL_ENABLED, {
mimetype: "application/x-xpinstall", referer: this.url.spec
})[0];
},
/**
* @see amIInstallTriggerInstaller.idl
*/
updateEnabled: function() {
return this.enabled();
},
/**
* @see amIInstallTriggerInstaller.idl
*/
install: function(aArgs, aCallback) {
if (!aArgs || typeof aArgs != "object")
throw new Error("Incorrect arguments passed to InstallTrigger.install()");
var params = {
installerId: this.installerId,
mimetype: "application/x-xpinstall",
referer: this.url.spec,
uris: [],
hashes: [],
names: [],
icons: [],
};
for (var name in aArgs) {
var item = aArgs[name];
if (typeof item === 'string') {
item = { URL: item };
} else if (!("URL" in item) || item.URL === undefined) {
throw new Error("Missing URL property for '" + name + "'");
}
// Resolve and validate urls
var url = this.resolveURL(item.URL);
if (!this.checkLoadURIFromScript(url))
throw new Error("insufficient permissions to install: " + url);
var iconUrl = null;
if ("IconURL" in item && item.IconURL !== undefined) {
iconUrl = this.resolveURL(item.IconURL);
if (!this.checkLoadURIFromScript(iconUrl)) {
iconUrl = null; // If page can't load the icon, just ignore it
}
}
params.uris.push(url.spec);
params.hashes.push("Hash" in item ? item.Hash : null);
params.names.push(name);
params.icons.push(iconUrl ? iconUrl.spec : null);
}
// Add callback Id, done here, so only if we actually got here
params.callbackId = manager.addCallback(aCallback, params.uris);
// Send message
return sendSyncMessage(MSG_INSTALL_ADDONS, params)[0];
},
/**
* @see amIInstallTriggerInstaller.idl
*/
startSoftwareUpdate: function(aUrl, aFlags) {
var url = gIoService.newURI(aUrl, null, null)
.QueryInterface(Ci.nsIURL).filename;
var object = {};
object[url] = { "URL": aUrl };
return this.install(object);
},
/**
* @see amIInstallTriggerInstaller.idl
*/
installChrome: function(aType, aUrl, aSkin) {
return this.startSoftwareUpdate(aUrl);
},
/**
* Resolves a URL in the context of our current window. We need to do
* this before sending URLs to the parent process.
*
* @param aUrl
* The url to resolve.
*
* @return A resolved, absolute nsURI object.
*/
resolveURL: function(aUrl) {
return gIoService.newURI(aUrl, null, this.url);
},
/**
* @see amInstallTrigger.cpp
* TODO: When e10s lands on m-c, consider removing amInstallTrigger.cpp
* See bug 571166
*/
checkLoadURIFromScript: function(aUri) {
var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
getService(Ci.nsIScriptSecurityManager);
var principal = this.window.document.nodePrincipal;
try {
secman.checkLoadURIWithPrincipal(principal, aUri,
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
return true;
}
catch(e) {
return false;
}
}
};
let sandbox = Cu.Sandbox(window);
let obj = Cu.evalInSandbox(
"(function (x) {\
var bind = Function.bind;\
return {\
enabled: bind.call(x.enabled, x),\
updateEnabled: bind.call(x.updateEnabled, x),\
install: bind.call(x.install, x),\
installChrome: bind.call(x.installChrome, x),\
startSoftwareUpdate: bind.call(x.startSoftwareUpdate, x)\
};\
})", sandbox)(chromeObject);
obj.SKIN = chromeObject.SKIN;
obj.LOCALE = chromeObject.LOCALE;
obj.CONTENT = chromeObject.CONTENT;
obj.PACKAGE = chromeObject.PACKAGE;
return obj;
};
/**
* Child part of InstallTrigger e10s handling.
*
* Sets up InstallTrigger for newly-created windows,
* that will relay messages for InstallTrigger
* activity. We also process the parameters for
* the InstallTrigger to proper parameters for
* amIWebInstaller.
*/
function InstallTriggerManager() {
this.callbacks = {};
addMessageListener(MSG_INSTALL_CALLBACK, this);
try {
// only if we live in a child process...
if (Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processType !== Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
// ... propagate JAR cache flush notifications across process boundaries
addMessageListener(MSG_JAR_FLUSH, function(msg) {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(msg.json);
Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService)
.notifyObservers(file, "flush-cache-entry", null);
});
}
} catch(e) {
Cu.reportError(e);
}
addEventListener("DOMWindowCreated", this, false);
var self = this;
addEventListener("unload", function() {
// Clean up all references, to help gc work quickly
self.callbacks = null;
}, false);
}
InstallTriggerManager.prototype = {
handleEvent: function handleEvent(aEvent) {
var window = aEvent.target.defaultView;
window.wrappedJSObject.__defineGetter__("InstallTrigger", function() {
// We do this in a getter, so that we create these objects
// only on demand (this is a potential concern, since
// otherwise we might add one per iframe, and keep them
// alive for as long as the tab is alive).
delete window.wrappedJSObject.InstallTrigger;
var installTrigger = createInstallTrigger(window);
window.wrappedJSObject.InstallTrigger = installTrigger;
return installTrigger;
});
},
/**
* Adds a callback to the list of callbacks we may receive messages
* about from the parent process. We save them here; only callback IDs
* are sent over IPC.
*
* @param callback
* The callback function
* @param urls
* The urls this callback function will receive responses for.
* After all the callbacks have arrived, we can forget about the
* callback.
*
* @return The callback ID, an integer identifying this callback.
*/
addCallback: function(aCallback, aUrls) {
if (!aCallback || typeof aCallback != "function")
return -1;
var callbackId = 0;
while (callbackId in this.callbacks)
callbackId++;
this.callbacks[callbackId] = {
callback: aCallback,
urls: aUrls.slice(0), // Clone the urls for our own use (it lets
// us know when no further callbacks will
// occur)
};
return callbackId;
},
/**
* Receives a message about a callback. Performs the actual callback
* (for the callback with the ID we are given). When
* all URLs are exhausted, can free the callbackId and linked stuff.
*
* @param message
* The IPC message. Contains the callback ID.
*
*/
receiveMessage: function(aMessage) {
var payload = aMessage.json;
var callbackId = payload.callbackId;
var url = payload.url;
var status = payload.status;
var callbackObj = this.callbacks[callbackId];
if (!callbackObj)
return;
try {
callbackObj.callback(url, status);
}
catch (e) {
dump("InstallTrigger callback threw an exception: " + e + "\n");
}
callbackObj.urls.splice(callbackObj.urls.indexOf(url), 1);
if (callbackObj.urls.length == 0)
this.callbacks[callbackId] = null;
},
};
var manager = new InstallTriggerManager();