-
Notifications
You must be signed in to change notification settings - Fork 2
/
pullstarter.js
201 lines (184 loc) · 6.45 KB
/
pullstarter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
/**
* A collection of helpers that allow you to register various things while
* making sure that they clean up after themselves when the add-on gets
* unloaded or its context (e.g. DOM windows) get destroyed otherwise.
*
* Call the various PullStarter.register* methods from your startup() function
* and/or other places in your code.
*/
let PullStarter = {
/**
* Unload everything that has been registered with PullStarter.
* This is called in PullStarter's default shutdown(), so if you're
* redefining that you want to make sure you call PullStarter.unload().
*/
_unloaders: [],
unload: function unload() {
this._unloaders.reverse();
this._unloaders.forEach(function(func) {
func.call(this);
}, this);
this._unloaders = [];
},
/**
* Register an unloader function.
*
* @param callback
* A function that performs some sort of unloading action.
* @param window [optional]
* A DOM window object that, when closed, will also call the unloader.
*
* @return a function that removes the unregisters unloader.
*/
registerUnloader: function registerUnloader(callback, window) {
let unloaders = this._unloaders;
// Wrap the callback in a function that ignores failures.
function unloader() {
try {
callback();
} catch (ex) {
// Ignore.
}
}
unloaders.push(unloader);
// Provide a way to remove the unloader.
function removeUnloader() {
let index = unloaders.indexOf(unloader);
if (index != -1) {
unloaders.splice(index, 1);
}
}
// If an associated window was specified, we want to call the
// unloader when the window dies, or when the extension unloads.
if (window) {
// That means when the window gets unloaded, we want to call the unloader
// and remove it from the global unloader list.
let onWindowUnload = function onWindowUnload() {
unloader();
removeUnloader();
};
window.addEventListener("unload", onWindowUnload, false);
// When the unloader is called, we want to remove the window unload event
// listener, too.
let origCallback = callback;
callback = function callback() {
window.removeEventListener("unload", onWindowUnload, false);
origCallback();
};
}
return removeUnloader;
},
/**
* Register the addon's directory as a resource protocol host. This will
* allow you to refer to files packaged in the add-on as
* resource://<host>/<filename>.
*
* @param host
* The name of the resource protocol host.
* @param data
* The add-on data object passed into the startup() function.
*/
registerResourceHost: function registerResourceHost(host, data) {
this._resProtocolHandler.setSubstitution(host, data.resourceURI);
this.registerUnloader(function () {
this._resProtocolHandler.setSubstitution(host, null);
});
},
/**
* Register an event handler on a DOM node.
*
* @param element
* The DOM node.
* @param event
* The name of the event, e.g. 'click'.
* @param callback
* The event handler function.
* @param capture
* Boolean flag to indicate whether to use capture or not.
*
* @return a function that, when called, removes the event handler again.
*
* @note When the window that the DOM node belongs to is closed, the
* event handler will automatically be removed. It will not be removed
* if the DOM node is removed from the document. The returned function
* must be called in this case.
*/
registerEventListener:
function registerEventListener(element, event, callback, capture) {
element.addEventListener(event, callback, !!capture);
let window = element.ownerDocument.defaultView;
function removeListener() {
element.removeEventListener(event, callback, !!capture);
}
let removeUnloader = this.registerUnloader(removeListener, window);
return function removeEventListener() {
removeListener();
removeUnloader();
};
},
/**
* Apply callback to all existing and future windows of a certain type.
*
* @param type
* The window type, e.g. "navigator:browser" for the browser window.
* @param callback
* The function to invoke. It will be called with the window object
* as its only parameter.
*/
watchWindows: function watchWindows(type, callback) {
// Wrap the callback in a function that ignores failures.
function watcher(window) {
try {
let documentElement = window.document.documentElement;
if (documentElement.getAttribute("windowtype") == type) {
callback(window);
}
} catch (ex) {
// Ignore.
}
}
// Wait for the window to finish loading before running the callback.
function runOnLoad(window) {
// Listen for one load event before checking the window type
window.addEventListener("load", function runOnce() {
window.removeEventListener("load", runOnce, false);
watcher(window);
}, false);
}
// Enumerating existing windows.
let windows = Services.wm.getEnumerator(null);
while (windows.hasMoreElements()) {
// Only run the watcher immediately if the window is completely loaded
let window = windows.getNext();
if (window.document.readyState == "complete") {
watcher(window);
} else {
// Wait for the window to load before continuing
runOnLoad(window);
}
}
// Watch for new browser windows opening.
function windowWatcher(subject, topic) {
if (topic == "domwindowopened") {
runOnLoad(subject);
}
}
Services.ww.registerNotification(windowWatcher);
this.registerUnloader(function () {
Services.ww.unregisterNotification(windowWatcher);
});
},
//TODO import + unload JSMs?
//TODO l10n stringbundles
//TODO stylesheets?
};
XPCOMUtils.defineLazyGetter(PullStarter, "_resProtocolHandler", function () {
return Services.io.getProtocolHandler("resource")
.QueryInterface(Components.interfaces.nsIResProtocolHandler);
});