Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
9 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
10 Cu.import("resource://gre/modules/Services.jsm");
11 Cu.import("resource://gre/modules/Preferences.jsm");
12 Cu.import("resource://gre/modules/Log.jsm");
14 const XPINSTALL_MIMETYPE = "application/x-xpinstall";
16 const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled";
17 const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage";
18 const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
21 let log = Log.repository.getLogger("AddonManager.InstallTrigger");
22 log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"];
24 function CallbackObject(id, callback, urls, mediator) {
25 this.id = id;
26 this.callback = callback;
27 this.urls = new Set(urls);
28 this.callCallback = function(url, status) {
29 try {
30 this.callback(url, status);
31 }
32 catch (e) {
33 log.warn("InstallTrigger callback threw an exception: " + e);
34 }
36 this.urls.delete(url);
37 if (this.urls.size == 0)
38 mediator._callbacks.delete(id);
39 };
40 }
42 function RemoteMediator(windowID) {
43 this._windowID = windowID;
44 this._lastCallbackID = 0;
45 this._callbacks = new Map();
46 this.mm = Cc["@mozilla.org/childprocessmessagemanager;1"]
47 .getService(Ci.nsISyncMessageSender);
48 this.mm.addWeakMessageListener(MSG_INSTALL_CALLBACK, this);
49 }
51 RemoteMediator.prototype = {
52 receiveMessage: function(message) {
53 if (message.name == MSG_INSTALL_CALLBACK) {
54 let payload = message.data;
55 let callbackHandler = this._callbacks.get(payload.callbackID);
56 if (callbackHandler) {
57 callbackHandler.callCallback(payload.url, payload.status);
58 }
59 }
60 },
62 enabled: function(url) {
63 let params = {
64 referer: url,
65 mimetype: XPINSTALL_MIMETYPE
66 };
67 return this.mm.sendSyncMessage(MSG_INSTALL_ENABLED, params)[0];
68 },
70 install: function(installs, referer, callback, window) {
71 let callbackID = this._addCallback(callback, installs.uris);
73 installs.mimetype = XPINSTALL_MIMETYPE;
74 installs.referer = referer;
75 installs.callbackID = callbackID;
77 return this.mm.sendSyncMessage(MSG_INSTALL_ADDONS, installs, {win: window})[0];
78 },
80 _addCallback: function(callback, urls) {
81 if (!callback || typeof callback != "function")
82 return -1;
84 let callbackID = this._windowID + "-" + ++this._lastCallbackID;
85 let callbackObject = new CallbackObject(callbackID, callback, urls, this);
86 this._callbacks.set(callbackID, callbackObject);
87 return callbackID;
88 },
90 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference])
91 };
94 function InstallTrigger() {
95 }
97 InstallTrigger.prototype = {
98 // Here be magic. We've declared ourselves as providing the
99 // nsIDOMGlobalPropertyInitializer interface, and are registered in the
100 // "JavaScript-global-property" category in the XPCOM category manager. This
101 // means that for newly created windows, XPCOM will createinstance this
102 // object, and then call init, passing in the window for which we need to
103 // provide an instance. We then initialize ourselves and return the webidl
104 // version of this object using the webidl-provided _create method, which
105 // XPCOM will then duly expose as a property value on the window. All this
106 // indirection is necessary because webidl does not (yet) support statics
107 // (bug 863952). See bug 926712 for more details about this implementation.
108 init: function(window) {
109 this._window = window;
110 this._principal = window.document.nodePrincipal;
111 this._url = window.document.documentURIObject;
113 window.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
114 let utils = window.getInterface(Components.interfaces.nsIDOMWindowUtils);
115 this._mediator = new RemoteMediator(utils.currentInnerWindowID);
117 return window.InstallTriggerImpl._create(window, this);
118 },
120 enabled: function() {
121 return this._mediator.enabled(this._url.spec);
122 },
124 updateEnabled: function() {
125 return this.enabled();
126 },
128 install: function(installs, callback) {
129 let installData = {
130 uris: [],
131 hashes: [],
132 names: [],
133 icons: [],
134 };
136 for (let name of Object.keys(installs)) {
137 let item = installs[name];
138 if (typeof item === "string") {
139 item = { URL: item };
140 }
141 if (!item.URL) {
142 throw new this._window.DOMError("Error", "Missing URL property for '" + name + "'");
143 }
145 let url = this._resolveURL(item.URL);
146 if (!this._checkLoadURIFromScript(url)) {
147 throw new this._window.DOMError("SecurityError", "Insufficient permissions to install: " + url.spec);
148 }
150 let iconUrl = null;
151 if (item.IconURL) {
152 iconUrl = this._resolveURL(item.IconURL);
153 if (!this._checkLoadURIFromScript(iconUrl)) {
154 iconUrl = null; // If page can't load the icon, just ignore it
155 }
156 }
158 installData.uris.push(url.spec);
159 installData.hashes.push(item.Hash || null);
160 installData.names.push(name);
161 installData.icons.push(iconUrl ? iconUrl.spec : null);
162 }
164 return this._mediator.install(installData, this._url.spec, callback, this._window);
165 },
167 startSoftwareUpdate: function(url, flags) {
168 let filename = Services.io.newURI(url, null, null)
169 .QueryInterface(Ci.nsIURL)
170 .filename;
171 let args = {};
172 args[filename] = { "URL": url };
173 return this.install(args);
174 },
176 installChrome: function(type, url, skin) {
177 return this.startSoftwareUpdate(url);
178 },
180 _resolveURL: function (url) {
181 return Services.io.newURI(url, null, this._url);
182 },
184 _checkLoadURIFromScript: function(uri) {
185 let secman = Services.scriptSecurityManager;
186 try {
187 secman.checkLoadURIWithPrincipal(this._principal,
188 uri,
189 secman.DISALLOW_INHERIT_PRINCIPAL);
190 return true;
191 }
192 catch(e) {
193 return false;
194 }
195 },
197 classID: Components.ID("{9df8ef2b-94da-45c9-ab9f-132eb55fddf1}"),
198 contractID: "@mozilla.org/addons/installtrigger;1",
199 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIDOMGlobalPropertyInitializer])
200 };
204 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InstallTrigger]);