1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/mozapps/extensions/amInstallTrigger.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,204 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; 1.11 + 1.12 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.13 +Cu.import("resource://gre/modules/Services.jsm"); 1.14 +Cu.import("resource://gre/modules/Preferences.jsm"); 1.15 +Cu.import("resource://gre/modules/Log.jsm"); 1.16 + 1.17 +const XPINSTALL_MIMETYPE = "application/x-xpinstall"; 1.18 + 1.19 +const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled"; 1.20 +const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; 1.21 +const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback"; 1.22 + 1.23 + 1.24 +let log = Log.repository.getLogger("AddonManager.InstallTrigger"); 1.25 +log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"]; 1.26 + 1.27 +function CallbackObject(id, callback, urls, mediator) { 1.28 + this.id = id; 1.29 + this.callback = callback; 1.30 + this.urls = new Set(urls); 1.31 + this.callCallback = function(url, status) { 1.32 + try { 1.33 + this.callback(url, status); 1.34 + } 1.35 + catch (e) { 1.36 + log.warn("InstallTrigger callback threw an exception: " + e); 1.37 + } 1.38 + 1.39 + this.urls.delete(url); 1.40 + if (this.urls.size == 0) 1.41 + mediator._callbacks.delete(id); 1.42 + }; 1.43 +} 1.44 + 1.45 +function RemoteMediator(windowID) { 1.46 + this._windowID = windowID; 1.47 + this._lastCallbackID = 0; 1.48 + this._callbacks = new Map(); 1.49 + this.mm = Cc["@mozilla.org/childprocessmessagemanager;1"] 1.50 + .getService(Ci.nsISyncMessageSender); 1.51 + this.mm.addWeakMessageListener(MSG_INSTALL_CALLBACK, this); 1.52 +} 1.53 + 1.54 +RemoteMediator.prototype = { 1.55 + receiveMessage: function(message) { 1.56 + if (message.name == MSG_INSTALL_CALLBACK) { 1.57 + let payload = message.data; 1.58 + let callbackHandler = this._callbacks.get(payload.callbackID); 1.59 + if (callbackHandler) { 1.60 + callbackHandler.callCallback(payload.url, payload.status); 1.61 + } 1.62 + } 1.63 + }, 1.64 + 1.65 + enabled: function(url) { 1.66 + let params = { 1.67 + referer: url, 1.68 + mimetype: XPINSTALL_MIMETYPE 1.69 + }; 1.70 + return this.mm.sendSyncMessage(MSG_INSTALL_ENABLED, params)[0]; 1.71 + }, 1.72 + 1.73 + install: function(installs, referer, callback, window) { 1.74 + let callbackID = this._addCallback(callback, installs.uris); 1.75 + 1.76 + installs.mimetype = XPINSTALL_MIMETYPE; 1.77 + installs.referer = referer; 1.78 + installs.callbackID = callbackID; 1.79 + 1.80 + return this.mm.sendSyncMessage(MSG_INSTALL_ADDONS, installs, {win: window})[0]; 1.81 + }, 1.82 + 1.83 + _addCallback: function(callback, urls) { 1.84 + if (!callback || typeof callback != "function") 1.85 + return -1; 1.86 + 1.87 + let callbackID = this._windowID + "-" + ++this._lastCallbackID; 1.88 + let callbackObject = new CallbackObject(callbackID, callback, urls, this); 1.89 + this._callbacks.set(callbackID, callbackObject); 1.90 + return callbackID; 1.91 + }, 1.92 + 1.93 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]) 1.94 +}; 1.95 + 1.96 + 1.97 +function InstallTrigger() { 1.98 +} 1.99 + 1.100 +InstallTrigger.prototype = { 1.101 + // Here be magic. We've declared ourselves as providing the 1.102 + // nsIDOMGlobalPropertyInitializer interface, and are registered in the 1.103 + // "JavaScript-global-property" category in the XPCOM category manager. This 1.104 + // means that for newly created windows, XPCOM will createinstance this 1.105 + // object, and then call init, passing in the window for which we need to 1.106 + // provide an instance. We then initialize ourselves and return the webidl 1.107 + // version of this object using the webidl-provided _create method, which 1.108 + // XPCOM will then duly expose as a property value on the window. All this 1.109 + // indirection is necessary because webidl does not (yet) support statics 1.110 + // (bug 863952). See bug 926712 for more details about this implementation. 1.111 + init: function(window) { 1.112 + this._window = window; 1.113 + this._principal = window.document.nodePrincipal; 1.114 + this._url = window.document.documentURIObject; 1.115 + 1.116 + window.QueryInterface(Components.interfaces.nsIInterfaceRequestor); 1.117 + let utils = window.getInterface(Components.interfaces.nsIDOMWindowUtils); 1.118 + this._mediator = new RemoteMediator(utils.currentInnerWindowID); 1.119 + 1.120 + return window.InstallTriggerImpl._create(window, this); 1.121 + }, 1.122 + 1.123 + enabled: function() { 1.124 + return this._mediator.enabled(this._url.spec); 1.125 + }, 1.126 + 1.127 + updateEnabled: function() { 1.128 + return this.enabled(); 1.129 + }, 1.130 + 1.131 + install: function(installs, callback) { 1.132 + let installData = { 1.133 + uris: [], 1.134 + hashes: [], 1.135 + names: [], 1.136 + icons: [], 1.137 + }; 1.138 + 1.139 + for (let name of Object.keys(installs)) { 1.140 + let item = installs[name]; 1.141 + if (typeof item === "string") { 1.142 + item = { URL: item }; 1.143 + } 1.144 + if (!item.URL) { 1.145 + throw new this._window.DOMError("Error", "Missing URL property for '" + name + "'"); 1.146 + } 1.147 + 1.148 + let url = this._resolveURL(item.URL); 1.149 + if (!this._checkLoadURIFromScript(url)) { 1.150 + throw new this._window.DOMError("SecurityError", "Insufficient permissions to install: " + url.spec); 1.151 + } 1.152 + 1.153 + let iconUrl = null; 1.154 + if (item.IconURL) { 1.155 + iconUrl = this._resolveURL(item.IconURL); 1.156 + if (!this._checkLoadURIFromScript(iconUrl)) { 1.157 + iconUrl = null; // If page can't load the icon, just ignore it 1.158 + } 1.159 + } 1.160 + 1.161 + installData.uris.push(url.spec); 1.162 + installData.hashes.push(item.Hash || null); 1.163 + installData.names.push(name); 1.164 + installData.icons.push(iconUrl ? iconUrl.spec : null); 1.165 + } 1.166 + 1.167 + return this._mediator.install(installData, this._url.spec, callback, this._window); 1.168 + }, 1.169 + 1.170 + startSoftwareUpdate: function(url, flags) { 1.171 + let filename = Services.io.newURI(url, null, null) 1.172 + .QueryInterface(Ci.nsIURL) 1.173 + .filename; 1.174 + let args = {}; 1.175 + args[filename] = { "URL": url }; 1.176 + return this.install(args); 1.177 + }, 1.178 + 1.179 + installChrome: function(type, url, skin) { 1.180 + return this.startSoftwareUpdate(url); 1.181 + }, 1.182 + 1.183 + _resolveURL: function (url) { 1.184 + return Services.io.newURI(url, null, this._url); 1.185 + }, 1.186 + 1.187 + _checkLoadURIFromScript: function(uri) { 1.188 + let secman = Services.scriptSecurityManager; 1.189 + try { 1.190 + secman.checkLoadURIWithPrincipal(this._principal, 1.191 + uri, 1.192 + secman.DISALLOW_INHERIT_PRINCIPAL); 1.193 + return true; 1.194 + } 1.195 + catch(e) { 1.196 + return false; 1.197 + } 1.198 + }, 1.199 + 1.200 + classID: Components.ID("{9df8ef2b-94da-45c9-ab9f-132eb55fddf1}"), 1.201 + contractID: "@mozilla.org/addons/installtrigger;1", 1.202 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIDOMGlobalPropertyInitializer]) 1.203 +}; 1.204 + 1.205 + 1.206 + 1.207 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InstallTrigger]);