Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 480 lines (395 sloc) 15.738 kb
8067027 @Mossop Make add-on restartless
authored
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is WebApp Tabs.
15 *
16 * The Initial Developer of the Original Code is
17 * Dave Townsend <dtownsend@oxymoronical.com>
18 * Portions created by the Initial Developer are Copyright (C) 2011
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Contributor(s):
22 *
23 * Alternatively, the contents of this file may be used under the terms of
24 * either the GNU General Public License Version 2 or later (the "GPL"), or
25 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26 * in which case the provisions of the GPL or the LGPL are applicable instead
27 * of those above. If you wish to allow use of your version of this file only
28 * under the terms of either the GPL or the LGPL, and not to allow others to
29 * use your version of this file under the terms of the MPL, indicate your
30 * decision by deleting the provisions above and replace them with the notice
31 * and other provisions required by the GPL or the LGPL. If you do not delete
32 * the provisions above, a recipient may use your version of this file under
33 * the terms of any one of the MPL, the GPL or the LGPL.
34 *
35 * ***** END LICENSE BLOCK ***** */
36
37 const EXPORTED_SYMBOLS = ["OverlayManager"];
38
39 Components.utils.import("resource://gre/modules/Services.jsm");
40 Components.utils.import("resource://webapptabs/modules/LogManager.jsm");
41 LogManager.createLogger(this, "OverlayManager");
42
43 const Cc = Components.classes;
44 const Ci = Components.interfaces;
45 const Ce = Components.Exception;
46 const Cr = Components.results;
923551d @Mossop Add component and category registration support to OverlayManager
authored
47 const Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
8067027 @Mossop Make add-on restartless
authored
48
ad388db @Mossop Add basic support for XUL overlays to OverlayManager
authored
49 const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
50
923551d @Mossop Add component and category registration support to OverlayManager
authored
51 function createSandbox(aPrincipal, aScriptURL, aPrototype) {
52 let args = {
53 sandboxName: aScriptURL
54 };
55
56 if (aPrototype)
57 args.sandboxPrototype = aPrototype;
58
59 let sandbox = Components.utils.Sandbox(aPrincipal, args);
60
61 try {
62 Components.utils.evalInSandbox(
63 "Components.classes['@mozilla.org/moz/jssubscript-loader;1']" +
64 ".createInstance(Components.interfaces.mozIJSSubScriptLoader)" +
65 ".loadSubScript('" + aScriptURL + "');", sandbox, "ECMAv5");
66 }
67 catch (e) {
68 WARN("Exception loading script " + aScriptURL, e);
69 }
70
71 return sandbox
72 }
73
8067027 @Mossop Make add-on restartless
authored
74 const OverlayManager = {
75 addOverlays: function(aOverlayList) {
76 OverlayManagerInternal.addOverlays(aOverlayList);
77 },
78
923551d @Mossop Add component and category registration support to OverlayManager
authored
79 addComponent: function(aCid, aComponentURL, aContract) {
80 OverlayManagerInternal.addComponent(aCid, aComponentURL, aContract);
81 },
82
83 addCategory: function(aCategory, aEntry, aValue) {
84 OverlayManagerInternal.addCategory(aCategory, aEntry, aValue);
85 },
86
98532b4 @Mossop Add API for getting the script overlay sandbox for a given window
authored
87 getScriptContext: function(aWindow, aScriptURL) {
88 return OverlayManagerInternal.getScriptContext(aWindow, aScriptURL);
89 },
90
8067027 @Mossop Make add-on restartless
authored
91 unload: function() {
92 OverlayManagerInternal.unload();
93 }
94 };
95
96 const OverlayManagerInternal = {
97 windowEntryMap: new WeakMap(),
98 windowEntries: {},
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
99 overlays: {},
923551d @Mossop Add component and category registration support to OverlayManager
authored
100 components: [],
101 categories: [],
8067027 @Mossop Make add-on restartless
authored
102
103 init: function() {
104 LOG("init");
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
105 Services.wm.addListener(this);
8067027 @Mossop Make add-on restartless
authored
106 },
107
108 unload: function() {
109 LOG("unload");
110 try {
111 Services.wm.removeListener(this);
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
112
113 for (let windowURL in this.windowEntries) {
114 this.windowEntries[windowURL].forEach(function(aWindowEntry) {
115 this.destroyWindowEntry(aWindowEntry);
116 }, this);
8067027 @Mossop Make add-on restartless
authored
117 }
923551d @Mossop Add component and category registration support to OverlayManager
authored
118
119 let cm = Cc["@mozilla.org/categorymanager;1"].
120 getService(Ci.nsICategoryManager);
121 this.categories.forEach(function(aEntry) {
122 cm.deleteCategoryEntry(aEntry[0], aEntry[1], false);
123 });
edb6c9e @Mossop Fix content policy unregistration
authored
124
125 this.components.forEach(function(aCid) {
126 let factory = Cm.getClassObject(aCid, Ci.nsIFactory);
127 Cm.unregisterFactory(aCid, factory);
128 });
8067027 @Mossop Make add-on restartless
authored
129 }
130 catch (e) {
131 ERROR("Exception during unload", e);
132 }
133 },
134
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
135 createWindowEntry: function(aDOMWindow, aOverlays) {
5ce6211 @Mossop Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
136 aDOMWindow.addEventListener("unload", this, false);
137
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
138 let windowURL = aDOMWindow.location.toString();
139 LOG("Creating window entry for " + windowURL);
8067027 @Mossop Make add-on restartless
authored
140 if (this.windowEntryMap.has(aDOMWindow))
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
141 throw new Ce("Already registered window entry for " + windowURL);
8067027 @Mossop Make add-on restartless
authored
142
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
143 if (!(windowURL in this.windowEntries))
144 this.windowEntries[windowURL] = [];
8067027 @Mossop Make add-on restartless
authored
145
146 let newEntry = {
147 window: aDOMWindow,
98532b4 @Mossop Add API for getting the script overlay sandbox for a given window
authored
148 scripts: {},
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
149 nodes: [],
8067027 @Mossop Make add-on restartless
authored
150 };
151
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
152 this.windowEntries[windowURL].push(newEntry);
8067027 @Mossop Make add-on restartless
authored
153 this.windowEntryMap.set(aDOMWindow, newEntry);
154
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
155 this.applyWindowEntryOverlays(newEntry, aOverlays);
156 return newEntry
8067027 @Mossop Make add-on restartless
authored
157 },
158
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
159 destroyWindowEntry: function(aWindowEntry) {
160 aWindowEntry.window.removeEventListener("unload", this, false);
5ce6211 @Mossop Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
161
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
162 let windowURL = aWindowEntry.window.location.toString();
163 LOG("Destroying window entry for " + windowURL);
8067027 @Mossop Make add-on restartless
authored
164
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
165 this.windowEntryMap.delete(aWindowEntry.window);
8067027 @Mossop Make add-on restartless
authored
166
98532b4 @Mossop Add API for getting the script overlay sandbox for a given window
authored
167 for (let [,sandbox] in Iterator(aWindowEntry.scripts)) {
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
168 try {
98532b4 @Mossop Add API for getting the script overlay sandbox for a given window
authored
169 if ("OverlayListener" in sandbox && "unload" in sandbox.OverlayListener)
170 sandbox.OverlayListener.unload();
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
171 }
172 catch (e) {
173 ERROR("Exception calling script unload listener", e);
174 }
98532b4 @Mossop Add API for getting the script overlay sandbox for a given window
authored
175 }
176 aWindowEntry.scripts = {};
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
177
178 aWindowEntry.nodes.forEach(function(aNode) {
179 aNode.parentNode.removeChild(aNode);
180 }, this);
181 aWindowEntry.nodes = [];
8067027 @Mossop Make add-on restartless
authored
182
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
183 if (!(windowURL in this.windowEntries))
184 throw new Ce("Missing window entry for " + windowURL);
185 let pos = this.windowEntries[windowURL].indexOf(aWindowEntry);
8067027 @Mossop Make add-on restartless
authored
186 if (pos == -1)
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
187 throw new Ce("Missing window entry for " + windowURL);
8067027 @Mossop Make add-on restartless
authored
188
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
189 this.windowEntries[windowURL].splice(pos, 1);
190 if (this.windowEntries[windowURL].length == 0)
191 delete this.windowEntries[windowURL];
192 },
193
194 applyWindowEntryOverlays: function(aWindowEntry, aOverlays) {
ad388db @Mossop Add basic support for XUL overlays to OverlayManager
authored
195 if ("documents" in aOverlays) {
196 aOverlays.documents.forEach(function(aDocumentURL) {
197 this.loadDocumentOverlay(aWindowEntry, aDocumentURL);
198 }, this);
199 }
200
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
201 if ("styles" in aOverlays) {
202 aOverlays.styles.forEach(function(aStyleURL) {
203 this.loadStyleOverlay(aWindowEntry, aStyleURL);
204 }, this);
205 }
206
207 if ("scripts" in aOverlays) {
208 aOverlays.scripts.forEach(function(aScriptURL) {
209 this.loadScriptOverlay(aWindowEntry, aScriptURL);
210 }, this);
211 }
8067027 @Mossop Make add-on restartless
authored
212 },
213
ad388db @Mossop Add basic support for XUL overlays to OverlayManager
authored
214 loadDocumentOverlay: function(aWindowEntry, aDocumentURL) {
215 LOG("Loading document overlay " + aDocumentURL);
216
217 // TODO make this async
218 let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
219 createInstance(Ci.nsIXMLHttpRequest);
220 xhr.open("GET", aDocumentURL, false);
221 xhr.send();
222
223 let overlayDoc = xhr.responseXML;
224 if (overlayDoc.documentElement.namespaceURI == XMLURI_PARSE_ERROR)
225 return;
226
227 let targetDoc = aWindowEntry.window.document;
228
229 function walkDocumentNodes(aDocument) {
230 let node = aDocument.documentElement;
231
232 while (node) {
233 let currentNode = node;
234
235 // If possible to descend then do so
236 if (node.firstChild) {
237 node = node.firstChild;
238 }
239 else {
240 // Otherwise find the next node in the document by walking up the tree
241 // until there is a nextSibling (or we hit the documentElement)
242 while (!node.nextSibling && node.parentNode != overlayDoc.documentElement)
243 node = node.parentNode;
244
245 // Select the nextSibling (or null if we hit the top)
246 node = node.nextSibling;
247 }
248
249 yield currentNode;
250 }
251 }
252
253 function elementChildren(aElement) {
254 let node = aElement.firstChild;
255 while (node) {
256 let currentNode = node;
257
258 node = node.nextSibling;
259
260 if (currentNode instanceof Ci.nsIDOMElement)
261 yield currentNode;
262 }
263 }
264
265 for (let node in walkDocumentNodes(overlayDoc)) {
266 // Remove the node if it is an empty text node
267 if (node.nodeType == Ci.nsIDOMNode.TEXT_NODE && node.nodeValue.trim() == "")
268 node.parentNode.removeChild(node);
269 }
270
271 for (let containerElement in elementChildren(overlayDoc.documentElement)) {
272 if (!containerElement.id)
273 continue;
274
275 let targetElement = targetDoc.getElementById(containerElement.id);
276 if (!targetElement || targetElement.localName != containerElement.localName)
277 continue;
278
279 // TODO apply attributes to the target element
280
281 for (let newElement in elementChildren(containerElement)) {
38b8290 @Mossop Add insertbefore and insertafter support to OverlayManager
authored
282 let insertBefore = null;
283
284 if (newElement.hasAttribute("insertbefore")) {
285 insertBefore = targetDoc.getElementById(newElement.getAttribute("insertbefore"));
286 if (insertBefore && insertBefore.parentNode != targetElement)
287 insertBefore = null;
288 }
289
290 if (!insertBefore && newElement.hasAttribute("insertafter")) {
291 insertBefore = targetDoc.getElementById(newElement.getAttribute("insertafter"));
292 if (insertBefore) {
293 if (insertBefore.parentNode != targetElement)
294 insertBefore = null
295 else
296 insertBefore = insertBefore.nextSibling;
297 }
298 }
299
300 targetElement.insertBefore(newElement, insertBefore);
ad388db @Mossop Add basic support for XUL overlays to OverlayManager
authored
301 aWindowEntry.nodes.push(newElement);
302 }
303 }
304 },
305
8067027 @Mossop Make add-on restartless
authored
306 loadStyleOverlay: function(aWindowEntry, aStyleURL) {
307 LOG("Loading style overlay " + aStyleURL);
308
96d7b71 @Mossop Use XML processing instructions for style nodes
authored
309 let doc = aWindowEntry.window.document;
310 let styleNode = doc.createProcessingInstruction("xml-stylesheet",
311 "href=\"" + aStyleURL + "\" " +
312 "type=\"text/css\"");
313 doc.insertBefore(styleNode, doc.documentElement);
8067027 @Mossop Make add-on restartless
authored
314
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
315 aWindowEntry.nodes.push(styleNode);
8067027 @Mossop Make add-on restartless
authored
316 },
317
318 loadScriptOverlay: function(aWindowEntry, aScriptURL) {
319 LOG("Loading script overlay " + aScriptURL);
320
923551d @Mossop Add component and category registration support to OverlayManager
authored
321 let sandbox = createSandbox(aWindowEntry.window, aScriptURL, aWindowEntry.window);
98532b4 @Mossop Add API for getting the script overlay sandbox for a given window
authored
322 aWindowEntry.scripts[aScriptURL] = sandbox;
8067027 @Mossop Make add-on restartless
authored
323
923551d @Mossop Add component and category registration support to OverlayManager
authored
324 if ("OverlayListener" in sandbox && "load" in sandbox.OverlayListener) {
325 try {
98532b4 @Mossop Add API for getting the script overlay sandbox for a given window
authored
326 sandbox.OverlayListener.load();
923551d @Mossop Add component and category registration support to OverlayManager
authored
327 }
328 catch (e) {
329 WARN("Exception calling script load event " + aScriptURL, e);
330 }
8067027 @Mossop Make add-on restartless
authored
331 }
332 },
333
334 addOverlays: function(aOverlayList) {
335 try {
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
336 // First check over the new overlays, merge them into the master list
337 // and if any are for already tracked windows apply them
98532b4 @Mossop Add API for getting the script overlay sandbox for a given window
authored
338 for (let [windowURL, newOverlays] in Iterator(aOverlayList)) {
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
339 let newOverlays = aOverlayList[windowURL];
8067027 @Mossop Make add-on restartless
authored
340
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
341 if (!(windowURL in this.overlays))
342 this.overlays[windowURL] = {};
343 let existingOverlays = this.overlays[windowURL];
8067027 @Mossop Make add-on restartless
authored
344
ad388db @Mossop Add basic support for XUL overlays to OverlayManager
authored
345 ["documents", "styles", "scripts"].forEach(function(aType) {
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
346 if (!(aType in newOverlays))
347 return;
8067027 @Mossop Make add-on restartless
authored
348
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
349 if (!(aType in existingOverlays))
350 existingOverlays[aType] = newOverlays[aType].slice(0);
351 else
352 existingOverlays[aType].push(newOverlays[aType]);
8067027 @Mossop Make add-on restartless
authored
353 }, this);
354
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
355 // Apply the new overlays to any already tracked windows
356 if (windowURL in this.windowEntries) {
357 this.windowEntries[windowURL].forEach(function(aWindowEntry) {
358 this.applyWindowEntryOverlays(aWindowEntry, newOverlays);
359 }, this);
360 }
361 }
8067027 @Mossop Make add-on restartless
authored
362
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
363 // Search over existing windows to see if any need to be tracked now
364 let windows = Services.wm.getEnumerator(null);
365 while (windows.hasMoreElements()) {
366 let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
367 let windowURL = domWindow.location.toString();
8067027 @Mossop Make add-on restartless
authored
368
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
369 // If we are adding overlays for this window and not already tracking
370 // this window then start to track it and add the new overlays
371 if ((windowURL in aOverlayList) && !(windowURL in this.windowEntries)) {
372 let windowEntry = this.createWindowEntry(domWindow, aOverlayList[windowURL]);
373 }
8067027 @Mossop Make add-on restartless
authored
374 }
375 }
376 catch (e) {
377 ERROR("Exception adding overlay list", e);
378 }
379 },
380
923551d @Mossop Add component and category registration support to OverlayManager
authored
381 addComponent: function(aCid, aComponentURL, aContract) {
382 aCid = Components.ID(aCid);
383 Cm.registerFactory(aCid, null, aContract, {
384 _sandbox: null,
385
386 createInstance: function(aOuter, aIID) {
387 if (!this._sandbox) {
388 let principal = Cc["@mozilla.org/systemprincipal;1"].
389 createInstance(Ci.nsIPrincipal);
390 this._sandbox = createSandbox(principal, aComponentURL);
391 }
392
393 if (!("NSGetFactory" in this._sandbox)) {
394 ERROR("Component " + aComponentURL + " is missing NSGetFactory");
395 throw Cr.NS_ERROR_FACTORY_NOT_REGISTERED;
396 }
397
398 try {
399 return this._sandbox.NSGetFactory(aCid).createInstance(aOuter, aIID);
400 }
401 catch (e) {
402 ERROR("Exception initialising component " + aContract + " from " + aComponentURL, e);
403 throw e;
404 }
405 }
406 });
407
408 this.components.push(aCid);
409 },
410
411 addCategory: function(aCategory, aEntry, aValue) {
412 let cm = Cc["@mozilla.org/categorymanager;1"].
413 getService(Ci.nsICategoryManager);
414 cm.addCategoryEntry(aCategory, aEntry, aValue, false, true);
415 this.categories.push([aCategory, aEntry]);
416 },
417
98532b4 @Mossop Add API for getting the script overlay sandbox for a given window
authored
418 getScriptContext: function(aDOMWindow, aScriptURL) {
419 if (!this.windowEntryMap.has(aDOMWindow))
420 return null;
421 let windowEntry = this.windowEntryMap.get(aDOMWindow);
422 if (!(aScriptURL in windowEntry.scripts))
423 return null;
424 return windowEntry.scripts[aScriptURL];
425 },
426
5ce6211 @Mossop Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
427 // nsIEventListener implementation
428 handleEvent: function(aEvent) {
429 try {
430 let domWindow = aEvent.currentTarget;
8067027 @Mossop Make add-on restartless
authored
431
5ce6211 @Mossop Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
432 switch (aEvent.type) {
433 case "load":
434 domWindow.removeEventListener("load", this, false);
aefb34d @Mossop Wait for the window to load before deciding whether to track it or not s...
authored
435 let windowURL = domWindow.location.toString();
436 // Track this window if there are overlays for it
2ea6716 @Mossop Defer loading overlays until after all the window's load events have run
authored
437 if (windowURL in this.overlays) {
438 let tm = Cc["@mozilla.org/thread-manager;1"].
439 getService(Ci.nsIThreadManager);
440
441 let overlays = this.overlays[windowURL];
442
443 // Defer adding overlays until immediately after the load events fire
444 tm.mainThread.dispatch({
445 run: function() {
446 OverlayManagerInternal.createWindowEntry(domWindow, overlays);
447 }
448 }, Ci.nsIThread.DISPATCH_NORMAL);
449 }
5ce6211 @Mossop Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
450 break;
451 case "unload":
9b903fc @Mossop Only keep references to windows that we actually have overlays for
authored
452 if (!this.windowEntryMap.has(domWindow)) {
453 ERROR("Saw unload event for unknown window " + domWindow.location);
454 return;
455 }
456 let windowEntry = this.windowEntryMap.get(aDOMWindow);
457 OverlayManagerInternal.destroyWindowEntry(windowEntry);
5ce6211 @Mossop Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
458 break;
8067027 @Mossop Make add-on restartless
authored
459 }
5ce6211 @Mossop Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
460 }
461 catch (e) {
462 ERROR("Error during window " + aEvent.type, e);
463 }
464 },
8067027 @Mossop Make add-on restartless
authored
465
5ce6211 @Mossop Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
466 // nsIWindowMediatorListener implementation
467 onOpenWindow: function(aXULWindow) {
468 let domWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
469 .getInterface(Ci.nsIDOMWindowInternal);
8067027 @Mossop Make add-on restartless
authored
470
aefb34d @Mossop Wait for the window to load before deciding whether to track it or not s...
authored
471 // We can't get the window's URL until it is loaded
472 domWindow.addEventListener("load", this, false);
8067027 @Mossop Make add-on restartless
authored
473 },
474
475 onWindowTitleChange: function() { },
476 onCloseWindow: function() { },
477 };
478
479 OverlayManagerInternal.init();
Something went wrong with that request. Please try again.