toolkit/mozapps/extensions/amInstallTrigger.js

changeset 0
6474c204b198
     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]);

mercurial