Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 365 lines (300 sloc) 12.031 kb
8067027 Dave Townsend 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;
47
ad388db Dave Townsend Add basic support for XUL overlays to OverlayManager
authored
48 const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
49
8067027 Dave Townsend Make add-on restartless
authored
50 const OverlayManager = {
51 addOverlays: function(aOverlayList) {
52 OverlayManagerInternal.addOverlays(aOverlayList);
53 },
54
55 unload: function() {
56 OverlayManagerInternal.unload();
57 }
58 };
59
60 const OverlayManagerInternal = {
61 windowEntryMap: new WeakMap(),
62 windowEntries: {},
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
63 overlays: {},
8067027 Dave Townsend Make add-on restartless
authored
64
65 init: function() {
66 LOG("init");
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
67 Services.wm.addListener(this);
8067027 Dave Townsend Make add-on restartless
authored
68 },
69
70 unload: function() {
71 LOG("unload");
72 try {
73 Services.wm.removeListener(this);
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
74
75 for (let windowURL in this.windowEntries) {
76 this.windowEntries[windowURL].forEach(function(aWindowEntry) {
77 this.destroyWindowEntry(aWindowEntry);
78 }, this);
8067027 Dave Townsend Make add-on restartless
authored
79 }
80 }
81 catch (e) {
82 ERROR("Exception during unload", e);
83 }
84 },
85
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
86 createWindowEntry: function(aDOMWindow, aOverlays) {
5ce6211 Dave Townsend Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
87 aDOMWindow.addEventListener("unload", this, false);
88
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
89 let windowURL = aDOMWindow.location.toString();
90 LOG("Creating window entry for " + windowURL);
8067027 Dave Townsend Make add-on restartless
authored
91 if (this.windowEntryMap.has(aDOMWindow))
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
92 throw new Ce("Already registered window entry for " + windowURL);
8067027 Dave Townsend Make add-on restartless
authored
93
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
94 if (!(windowURL in this.windowEntries))
95 this.windowEntries[windowURL] = [];
8067027 Dave Townsend Make add-on restartless
authored
96
97 let newEntry = {
98 window: aDOMWindow,
99 scripts: [],
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
100 nodes: [],
8067027 Dave Townsend Make add-on restartless
authored
101 };
102
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
103 this.windowEntries[windowURL].push(newEntry);
8067027 Dave Townsend Make add-on restartless
authored
104 this.windowEntryMap.set(aDOMWindow, newEntry);
105
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
106 this.applyWindowEntryOverlays(newEntry, aOverlays);
107 return newEntry
8067027 Dave Townsend Make add-on restartless
authored
108 },
109
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
110 destroyWindowEntry: function(aWindowEntry) {
111 aWindowEntry.window.removeEventListener("unload", this, false);
5ce6211 Dave Townsend Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
112
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
113 let windowURL = aWindowEntry.window.location.toString();
114 LOG("Destroying window entry for " + windowURL);
8067027 Dave Townsend Make add-on restartless
authored
115
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
116 this.windowEntryMap.delete(aWindowEntry.window);
8067027 Dave Townsend Make add-on restartless
authored
117
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
118 aWindowEntry.scripts.forEach(function(aSandbox) {
119 try {
120 if ("OverlayListener" in aSandbox && "unload" in aSandbox.OverlayListener)
121 aSandbox.OverlayListener.unload();
122 }
123 catch (e) {
124 ERROR("Exception calling script unload listener", e);
125 }
126 }, this);
127 aWindowEntry.scripts = [];
128
129 aWindowEntry.nodes.forEach(function(aNode) {
130 aNode.parentNode.removeChild(aNode);
131 }, this);
132 aWindowEntry.nodes = [];
8067027 Dave Townsend Make add-on restartless
authored
133
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
134 if (!(windowURL in this.windowEntries))
135 throw new Ce("Missing window entry for " + windowURL);
136 let pos = this.windowEntries[windowURL].indexOf(aWindowEntry);
8067027 Dave Townsend Make add-on restartless
authored
137 if (pos == -1)
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
138 throw new Ce("Missing window entry for " + windowURL);
8067027 Dave Townsend Make add-on restartless
authored
139
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
140 this.windowEntries[windowURL].splice(pos, 1);
141 if (this.windowEntries[windowURL].length == 0)
142 delete this.windowEntries[windowURL];
143 },
144
145 applyWindowEntryOverlays: function(aWindowEntry, aOverlays) {
ad388db Dave Townsend Add basic support for XUL overlays to OverlayManager
authored
146 if ("documents" in aOverlays) {
147 aOverlays.documents.forEach(function(aDocumentURL) {
148 this.loadDocumentOverlay(aWindowEntry, aDocumentURL);
149 }, this);
150 }
151
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
152 if ("styles" in aOverlays) {
153 aOverlays.styles.forEach(function(aStyleURL) {
154 this.loadStyleOverlay(aWindowEntry, aStyleURL);
155 }, this);
156 }
157
158 if ("scripts" in aOverlays) {
159 aOverlays.scripts.forEach(function(aScriptURL) {
160 this.loadScriptOverlay(aWindowEntry, aScriptURL);
161 }, this);
162 }
8067027 Dave Townsend Make add-on restartless
authored
163 },
164
ad388db Dave Townsend Add basic support for XUL overlays to OverlayManager
authored
165 loadDocumentOverlay: function(aWindowEntry, aDocumentURL) {
166 LOG("Loading document overlay " + aDocumentURL);
167
168 // TODO make this async
169 let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
170 createInstance(Ci.nsIXMLHttpRequest);
171 xhr.open("GET", aDocumentURL, false);
172 xhr.send();
173
174 let overlayDoc = xhr.responseXML;
175 if (overlayDoc.documentElement.namespaceURI == XMLURI_PARSE_ERROR)
176 return;
177
178 let targetDoc = aWindowEntry.window.document;
179
180 function walkDocumentNodes(aDocument) {
181 let node = aDocument.documentElement;
182
183 while (node) {
184 let currentNode = node;
185
186 // If possible to descend then do so
187 if (node.firstChild) {
188 node = node.firstChild;
189 }
190 else {
191 // Otherwise find the next node in the document by walking up the tree
192 // until there is a nextSibling (or we hit the documentElement)
193 while (!node.nextSibling && node.parentNode != overlayDoc.documentElement)
194 node = node.parentNode;
195
196 // Select the nextSibling (or null if we hit the top)
197 node = node.nextSibling;
198 }
199
200 yield currentNode;
201 }
202 }
203
204 function elementChildren(aElement) {
205 let node = aElement.firstChild;
206 while (node) {
207 let currentNode = node;
208
209 node = node.nextSibling;
210
211 if (currentNode instanceof Ci.nsIDOMElement)
212 yield currentNode;
213 }
214 }
215
216 for (let node in walkDocumentNodes(overlayDoc)) {
217 // Remove the node if it is an empty text node
218 if (node.nodeType == Ci.nsIDOMNode.TEXT_NODE && node.nodeValue.trim() == "")
219 node.parentNode.removeChild(node);
220 }
221
222 for (let containerElement in elementChildren(overlayDoc.documentElement)) {
223 if (!containerElement.id)
224 continue;
225
226 let targetElement = targetDoc.getElementById(containerElement.id);
227 if (!targetElement || targetElement.localName != containerElement.localName)
228 continue;
229
230 // TODO apply attributes to the target element
231
232 for (let newElement in elementChildren(containerElement)) {
233 // TODO respect insertbefore and insertafter
234 targetElement.appendChild(newElement);
235 aWindowEntry.nodes.push(newElement);
236 }
237 }
238 },
239
8067027 Dave Townsend Make add-on restartless
authored
240 loadStyleOverlay: function(aWindowEntry, aStyleURL) {
241 LOG("Loading style overlay " + aStyleURL);
242
96d7b71 Dave Townsend Use XML processing instructions for style nodes
authored
243 let doc = aWindowEntry.window.document;
244 let styleNode = doc.createProcessingInstruction("xml-stylesheet",
245 "href=\"" + aStyleURL + "\" " +
246 "type=\"text/css\"");
247 doc.insertBefore(styleNode, doc.documentElement);
8067027 Dave Townsend Make add-on restartless
authored
248
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
249 aWindowEntry.nodes.push(styleNode);
8067027 Dave Townsend Make add-on restartless
authored
250 },
251
252 loadScriptOverlay: function(aWindowEntry, aScriptURL) {
253 LOG("Loading script overlay " + aScriptURL);
254
255 let sandbox = Components.utils.Sandbox(aWindowEntry.window, {
256 sandboxName: aScriptURL,
257 sandboxPrototype: aWindowEntry.window
258 });
259
260 try {
261 Components.utils.evalInSandbox(
262 "Components.classes['@mozilla.org/moz/jssubscript-loader;1']" +
263 ".createInstance(Components.interfaces.mozIJSSubScriptLoader)" +
264 ".loadSubScript('" + aScriptURL + "');", sandbox, "ECMAv5");
265
266 if ("OverlayListener" in sandbox && "load" in sandbox.OverlayListener)
267 sandbox.OverlayListener.load();
268 }
269 catch (e) {
270 WARN("Exception loading script overlay " + aScriptURL, e);
271 }
272
273 aWindowEntry.scripts.push(sandbox);
274 },
275
276 addOverlays: function(aOverlayList) {
277 try {
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
278 // First check over the new overlays, merge them into the master list
279 // and if any are for already tracked windows apply them
8067027 Dave Townsend Make add-on restartless
authored
280 for (windowURL in aOverlayList) {
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
281 let newOverlays = aOverlayList[windowURL];
8067027 Dave Townsend Make add-on restartless
authored
282
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
283 if (!(windowURL in this.overlays))
284 this.overlays[windowURL] = {};
285 let existingOverlays = this.overlays[windowURL];
8067027 Dave Townsend Make add-on restartless
authored
286
ad388db Dave Townsend Add basic support for XUL overlays to OverlayManager
authored
287 ["documents", "styles", "scripts"].forEach(function(aType) {
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
288 if (!(aType in newOverlays))
289 return;
8067027 Dave Townsend Make add-on restartless
authored
290
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
291 if (!(aType in existingOverlays))
292 existingOverlays[aType] = newOverlays[aType].slice(0);
293 else
294 existingOverlays[aType].push(newOverlays[aType]);
8067027 Dave Townsend Make add-on restartless
authored
295 }, this);
296
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
297 // Apply the new overlays to any already tracked windows
298 if (windowURL in this.windowEntries) {
299 this.windowEntries[windowURL].forEach(function(aWindowEntry) {
300 this.applyWindowEntryOverlays(aWindowEntry, newOverlays);
301 }, this);
302 }
303 }
8067027 Dave Townsend Make add-on restartless
authored
304
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
305 // Search over existing windows to see if any need to be tracked now
306 let windows = Services.wm.getEnumerator(null);
307 while (windows.hasMoreElements()) {
308 let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);
309 let windowURL = domWindow.location.toString();
8067027 Dave Townsend Make add-on restartless
authored
310
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
311 // If we are adding overlays for this window and not already tracking
312 // this window then start to track it and add the new overlays
313 if ((windowURL in aOverlayList) && !(windowURL in this.windowEntries)) {
314 let windowEntry = this.createWindowEntry(domWindow, aOverlayList[windowURL]);
315 }
8067027 Dave Townsend Make add-on restartless
authored
316 }
317 }
318 catch (e) {
319 ERROR("Exception adding overlay list", e);
320 }
321 },
322
5ce6211 Dave Townsend Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
323 // nsIEventListener implementation
324 handleEvent: function(aEvent) {
325 try {
326 let domWindow = aEvent.currentTarget;
8067027 Dave Townsend Make add-on restartless
authored
327
5ce6211 Dave Townsend Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
328 switch (aEvent.type) {
329 case "load":
330 domWindow.removeEventListener("load", this, false);
aefb34d Dave Townsend Wait for the window to load before deciding whether to track it or not s...
authored
331 let windowURL = domWindow.location.toString();
332 // Track this window if there are overlays for it
333 if (windowURL in this.overlays)
334 OverlayManagerInternal.createWindowEntry(domWindow, this.overlays[windowURL]);
5ce6211 Dave Townsend Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
335 break;
336 case "unload":
9b903fc Dave Townsend Only keep references to windows that we actually have overlays for
authored
337 if (!this.windowEntryMap.has(domWindow)) {
338 ERROR("Saw unload event for unknown window " + domWindow.location);
339 return;
340 }
341 let windowEntry = this.windowEntryMap.get(aDOMWindow);
342 OverlayManagerInternal.destroyWindowEntry(windowEntry);
5ce6211 Dave Townsend Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
343 break;
8067027 Dave Townsend Make add-on restartless
authored
344 }
5ce6211 Dave Townsend Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
345 }
346 catch (e) {
347 ERROR("Error during window " + aEvent.type, e);
348 }
349 },
8067027 Dave Townsend Make add-on restartless
authored
350
5ce6211 Dave Townsend Fix detecting opening and closing of windows in OverlayManager. Closes #...
authored
351 // nsIWindowMediatorListener implementation
352 onOpenWindow: function(aXULWindow) {
353 let domWindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
354 .getInterface(Ci.nsIDOMWindowInternal);
8067027 Dave Townsend Make add-on restartless
authored
355
aefb34d Dave Townsend Wait for the window to load before deciding whether to track it or not s...
authored
356 // We can't get the window's URL until it is loaded
357 domWindow.addEventListener("load", this, false);
8067027 Dave Townsend Make add-on restartless
authored
358 },
359
360 onWindowTitleChange: function() { },
361 onCloseWindow: function() { },
362 };
363
364 OverlayManagerInternal.init();
Something went wrong with that request. Please try again.