|
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/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const {classes: Cc, interfaces: Ci, utils: Cu} = Components; |
|
8 |
|
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"); |
|
13 |
|
14 const XPINSTALL_MIMETYPE = "application/x-xpinstall"; |
|
15 |
|
16 const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled"; |
|
17 const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage"; |
|
18 const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback"; |
|
19 |
|
20 |
|
21 let log = Log.repository.getLogger("AddonManager.InstallTrigger"); |
|
22 log.level = Log.Level[Preferences.get("extensions.logging.enabled", false) ? "Warn" : "Trace"]; |
|
23 |
|
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 } |
|
35 |
|
36 this.urls.delete(url); |
|
37 if (this.urls.size == 0) |
|
38 mediator._callbacks.delete(id); |
|
39 }; |
|
40 } |
|
41 |
|
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 } |
|
50 |
|
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 }, |
|
61 |
|
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 }, |
|
69 |
|
70 install: function(installs, referer, callback, window) { |
|
71 let callbackID = this._addCallback(callback, installs.uris); |
|
72 |
|
73 installs.mimetype = XPINSTALL_MIMETYPE; |
|
74 installs.referer = referer; |
|
75 installs.callbackID = callbackID; |
|
76 |
|
77 return this.mm.sendSyncMessage(MSG_INSTALL_ADDONS, installs, {win: window})[0]; |
|
78 }, |
|
79 |
|
80 _addCallback: function(callback, urls) { |
|
81 if (!callback || typeof callback != "function") |
|
82 return -1; |
|
83 |
|
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 }, |
|
89 |
|
90 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]) |
|
91 }; |
|
92 |
|
93 |
|
94 function InstallTrigger() { |
|
95 } |
|
96 |
|
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; |
|
112 |
|
113 window.QueryInterface(Components.interfaces.nsIInterfaceRequestor); |
|
114 let utils = window.getInterface(Components.interfaces.nsIDOMWindowUtils); |
|
115 this._mediator = new RemoteMediator(utils.currentInnerWindowID); |
|
116 |
|
117 return window.InstallTriggerImpl._create(window, this); |
|
118 }, |
|
119 |
|
120 enabled: function() { |
|
121 return this._mediator.enabled(this._url.spec); |
|
122 }, |
|
123 |
|
124 updateEnabled: function() { |
|
125 return this.enabled(); |
|
126 }, |
|
127 |
|
128 install: function(installs, callback) { |
|
129 let installData = { |
|
130 uris: [], |
|
131 hashes: [], |
|
132 names: [], |
|
133 icons: [], |
|
134 }; |
|
135 |
|
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 } |
|
144 |
|
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 } |
|
149 |
|
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 } |
|
157 |
|
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 } |
|
163 |
|
164 return this._mediator.install(installData, this._url.spec, callback, this._window); |
|
165 }, |
|
166 |
|
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 }, |
|
175 |
|
176 installChrome: function(type, url, skin) { |
|
177 return this.startSoftwareUpdate(url); |
|
178 }, |
|
179 |
|
180 _resolveURL: function (url) { |
|
181 return Services.io.newURI(url, null, this._url); |
|
182 }, |
|
183 |
|
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 }, |
|
196 |
|
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 }; |
|
201 |
|
202 |
|
203 |
|
204 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InstallTrigger]); |