michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/Preferences.jsm"); michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: michael@0: const XPINSTALL_MIMETYPE = "application/x-xpinstall"; michael@0: michael@0: const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled"; michael@0: const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; michael@0: const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback"; michael@0: michael@0: michael@0: let log = Log.repository.getLogger("AddonManager.InstallTrigger"); michael@0: log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"]; michael@0: michael@0: function CallbackObject(id, callback, urls, mediator) { michael@0: this.id = id; michael@0: this.callback = callback; michael@0: this.urls = new Set(urls); michael@0: this.callCallback = function(url, status) { michael@0: try { michael@0: this.callback(url, status); michael@0: } michael@0: catch (e) { michael@0: log.warn("InstallTrigger callback threw an exception: " + e); michael@0: } michael@0: michael@0: this.urls.delete(url); michael@0: if (this.urls.size == 0) michael@0: mediator._callbacks.delete(id); michael@0: }; michael@0: } michael@0: michael@0: function RemoteMediator(windowID) { michael@0: this._windowID = windowID; michael@0: this._lastCallbackID = 0; michael@0: this._callbacks = new Map(); michael@0: this.mm = Cc["@mozilla.org/childprocessmessagemanager;1"] michael@0: .getService(Ci.nsISyncMessageSender); michael@0: this.mm.addWeakMessageListener(MSG_INSTALL_CALLBACK, this); michael@0: } michael@0: michael@0: RemoteMediator.prototype = { michael@0: receiveMessage: function(message) { michael@0: if (message.name == MSG_INSTALL_CALLBACK) { michael@0: let payload = message.data; michael@0: let callbackHandler = this._callbacks.get(payload.callbackID); michael@0: if (callbackHandler) { michael@0: callbackHandler.callCallback(payload.url, payload.status); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: enabled: function(url) { michael@0: let params = { michael@0: referer: url, michael@0: mimetype: XPINSTALL_MIMETYPE michael@0: }; michael@0: return this.mm.sendSyncMessage(MSG_INSTALL_ENABLED, params)[0]; michael@0: }, michael@0: michael@0: install: function(installs, referer, callback, window) { michael@0: let callbackID = this._addCallback(callback, installs.uris); michael@0: michael@0: installs.mimetype = XPINSTALL_MIMETYPE; michael@0: installs.referer = referer; michael@0: installs.callbackID = callbackID; michael@0: michael@0: return this.mm.sendSyncMessage(MSG_INSTALL_ADDONS, installs, {win: window})[0]; michael@0: }, michael@0: michael@0: _addCallback: function(callback, urls) { michael@0: if (!callback || typeof callback != "function") michael@0: return -1; michael@0: michael@0: let callbackID = this._windowID + "-" + ++this._lastCallbackID; michael@0: let callbackObject = new CallbackObject(callbackID, callback, urls, this); michael@0: this._callbacks.set(callbackID, callbackObject); michael@0: return callbackID; michael@0: }, michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]) michael@0: }; michael@0: michael@0: michael@0: function InstallTrigger() { michael@0: } michael@0: michael@0: InstallTrigger.prototype = { michael@0: // Here be magic. We've declared ourselves as providing the michael@0: // nsIDOMGlobalPropertyInitializer interface, and are registered in the michael@0: // "JavaScript-global-property" category in the XPCOM category manager. This michael@0: // means that for newly created windows, XPCOM will createinstance this michael@0: // object, and then call init, passing in the window for which we need to michael@0: // provide an instance. We then initialize ourselves and return the webidl michael@0: // version of this object using the webidl-provided _create method, which michael@0: // XPCOM will then duly expose as a property value on the window. All this michael@0: // indirection is necessary because webidl does not (yet) support statics michael@0: // (bug 863952). See bug 926712 for more details about this implementation. michael@0: init: function(window) { michael@0: this._window = window; michael@0: this._principal = window.document.nodePrincipal; michael@0: this._url = window.document.documentURIObject; michael@0: michael@0: window.QueryInterface(Components.interfaces.nsIInterfaceRequestor); michael@0: let utils = window.getInterface(Components.interfaces.nsIDOMWindowUtils); michael@0: this._mediator = new RemoteMediator(utils.currentInnerWindowID); michael@0: michael@0: return window.InstallTriggerImpl._create(window, this); michael@0: }, michael@0: michael@0: enabled: function() { michael@0: return this._mediator.enabled(this._url.spec); michael@0: }, michael@0: michael@0: updateEnabled: function() { michael@0: return this.enabled(); michael@0: }, michael@0: michael@0: install: function(installs, callback) { michael@0: let installData = { michael@0: uris: [], michael@0: hashes: [], michael@0: names: [], michael@0: icons: [], michael@0: }; michael@0: michael@0: for (let name of Object.keys(installs)) { michael@0: let item = installs[name]; michael@0: if (typeof item === "string") { michael@0: item = { URL: item }; michael@0: } michael@0: if (!item.URL) { michael@0: throw new this._window.DOMError("Error", "Missing URL property for '" + name + "'"); michael@0: } michael@0: michael@0: let url = this._resolveURL(item.URL); michael@0: if (!this._checkLoadURIFromScript(url)) { michael@0: throw new this._window.DOMError("SecurityError", "Insufficient permissions to install: " + url.spec); michael@0: } michael@0: michael@0: let iconUrl = null; michael@0: if (item.IconURL) { michael@0: iconUrl = this._resolveURL(item.IconURL); michael@0: if (!this._checkLoadURIFromScript(iconUrl)) { michael@0: iconUrl = null; // If page can't load the icon, just ignore it michael@0: } michael@0: } michael@0: michael@0: installData.uris.push(url.spec); michael@0: installData.hashes.push(item.Hash || null); michael@0: installData.names.push(name); michael@0: installData.icons.push(iconUrl ? iconUrl.spec : null); michael@0: } michael@0: michael@0: return this._mediator.install(installData, this._url.spec, callback, this._window); michael@0: }, michael@0: michael@0: startSoftwareUpdate: function(url, flags) { michael@0: let filename = Services.io.newURI(url, null, null) michael@0: .QueryInterface(Ci.nsIURL) michael@0: .filename; michael@0: let args = {}; michael@0: args[filename] = { "URL": url }; michael@0: return this.install(args); michael@0: }, michael@0: michael@0: installChrome: function(type, url, skin) { michael@0: return this.startSoftwareUpdate(url); michael@0: }, michael@0: michael@0: _resolveURL: function (url) { michael@0: return Services.io.newURI(url, null, this._url); michael@0: }, michael@0: michael@0: _checkLoadURIFromScript: function(uri) { michael@0: let secman = Services.scriptSecurityManager; michael@0: try { michael@0: secman.checkLoadURIWithPrincipal(this._principal, michael@0: uri, michael@0: secman.DISALLOW_INHERIT_PRINCIPAL); michael@0: return true; michael@0: } michael@0: catch(e) { michael@0: return false; michael@0: } michael@0: }, michael@0: michael@0: classID: Components.ID("{9df8ef2b-94da-45c9-ab9f-132eb55fddf1}"), michael@0: contractID: "@mozilla.org/addons/installtrigger;1", michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIDOMGlobalPropertyInitializer]) michael@0: }; michael@0: michael@0: michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InstallTrigger]);