This repository has been archived by the owner on May 26, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 39
/
Copy pathbootstrap.js
238 lines (206 loc) · 8.53 KB
/
bootstrap.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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
const ADDON_ID = '@min-vid';
const WM = Cc['@mozilla.org/appshell/window-mediator;1'].
getService(Ci.nsIWindowMediator);
XPCOMUtils.defineLazyModuleGetter(this, 'setTimeout',
'resource://gre/modules/Timer.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'clearTimeout',
'resource://gre/modules/Timer.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'topify',
'chrome://minvid-lib/content/topify.js');
XPCOMUtils.defineLazyModuleGetter(this, 'DraggableElement',
'chrome://minvid-lib/content/dragging-utils.js');
XPCOMUtils.defineLazyModuleGetter(this, 'LegacyExtensionsUtils',
'resource://gre/modules/LegacyExtensionsUtils.jsm');
const LOCATION = { x: '30%', y: '30%' };
// TODO: consolidate with webextension/manifest.json
let DIMENSIONS = {
height: 260,
width: 400,
minimizedHeight: 100
};
let commandPollTimer;
// TODO: if mvWindow changes, we need to destroy and create the player.
// This must be why we get those dead object errors. Note that mvWindow
// is passed into the DraggableElement constructor, could be a source of
// those errors. Maybe pass a getter instead of a window reference.
let mvWindow, webExtPort; // global port for communication with webextension
XPCOMUtils.defineLazyModuleGetter(this, 'AddonManager',
'resource://gre/modules/AddonManager.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Console',
'resource://gre/modules/Console.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'Services',
'resource://gre/modules/Services.jsm');
XPCOMUtils.defineLazyModuleGetter(this, 'LegacyExtensionsUtils',
'resource://gre/modules/LegacyExtensionsUtils.jsm');
function startup(data, reason) { // eslint-disable-line no-unused-vars
if (data.webExtension.started) return;
data.webExtension.startup(reason).then(api => {
api.browser.runtime.onConnect.addListener(port => {
webExtPort = port;
webExtPort.onMessage.addListener((msg) => {
if (msg.content === 'window:send') send(msg.data);
else if (msg.content === 'window:prepare') updateWindow();
else if (msg.content === 'window:close') closeWindow();
else if (msg.content === 'window:minimize') minimize();
else if (msg.content === 'window:maximize') maximize();
else if (msg.content === 'window:dimensions:update') setDimensions(msg.data);
});
});
});
}
function shutdown(data, reason) { // eslint-disable-line no-unused-vars
closeWindow();
LegacyExtensionsUtils.getEmbeddedExtensionFor({
id: ADDON_ID,
resourceURI: data.resourceURI
}).shutdown(reason);
}
// These are mandatory in bootstrap.js, even if unused
function install(data, reason) {} // eslint-disable-line no-unused-vars
function uninstall(data, reason) {}// eslint-disable-line no-unused-vars
function updateWindow() {
return mvWindow || create();
}
function setDimensions(dimensions) {
DIMENSIONS = Object.assign(DIMENSIONS, dimensions);
}
/*
WINDOW UTILS
need to go back into own file
*/
// waits till the window is ready, then calls callbacks.
function whenReady(cb) {
// TODO: instead of setting timeout for each callback, just poll,
// then call all callbacks.
if (mvWindow && 'AppData' in mvWindow.wrappedJSObject) return cb();
setTimeout(() => { whenReady(cb); }, 25);
}
// I can't get frame scripts working, so instead we just set global state directly in react. fml
function send(msg) {
whenReady(() => {
const newData = Object.assign(mvWindow.wrappedJSObject.AppData, msg);
if (newData.confirm) maximize();
mvWindow.wrappedJSObject.AppData = newData;
});
}
// Detecting when the window is closed is surprisingly difficult. If hotkeys
// close the window, no detectable event is fired. Instead, we have to listen
// for the nsIObserver event fired when _any_ XUL window is closed, then loop
// over all windows and look for the minvid window.
const onWindowClosed = () => {
// Note: we pass null here because minvid window is not of type 'navigator:browser'
const enumerator = Services.wm.getEnumerator(null);
let minvidExists = false;
while (enumerator.hasMoreElements()) {
const win = enumerator.getNext();
if (win.name === 'min-vid') {
minvidExists = true;
break;
}
}
if (!minvidExists) closeWindow();
};
Services.obs.addObserver(onWindowClosed, 'xul-window-destroyed', false); // eslint-disable-line mozilla/no-useless-parameters
// This handles the case where the min vid window is kept open
// after closing the last firefox window.
function closeRequested() {
destroy(true);
}
Services.obs.addObserver(closeRequested, 'browser-lastwindow-close-requested', false); // eslint-disable-line mozilla/no-useless-parameters
function closeWindow() {
// If the window is gone, a 'dead object' error will be thrown; discard it.
try {
mvWindow && mvWindow.close();
} catch (ex) {} // eslint-disable-line no-empty
// stop communication
clearTimeout(commandPollTimer);
commandPollTimer = null;
// clear the window pointer
mvWindow = null;
// TODO: do we need to manually tear down frame scripts?
}
function create() {
if (mvWindow) return mvWindow;
const window = WM.getMostRecentWindow('navigator:browser');
// TODO: pass correct dimensions and location
const windowArgs = `left=${LOCATION.x},top=${LOCATION.y},chrome,dialog=no,width=${DIMENSIONS.width},height=${DIMENSIONS.height},titlebar=no`;
// const windowArgs = `left=${x},top=${y},chrome,dialog=no,width=${prefs.width},height=${prefs.height},titlebar=no`;
// implicit assignment to mvWindow global
mvWindow = window.open('resource://minvid-data/default.html', 'min-vid', windowArgs);
// once the window's ready, make it always topmost
whenReady(() => { topify(mvWindow); });
initCommunication();
whenReady(() => { makeDraggable(); });
return mvWindow;
}
function initCommunication() {
let errorCount = 0;
// When the window's ready, start polling for pending commands
function pollForCommands() {
let cmd;
try {
cmd = mvWindow.wrappedJSObject.pendingCommands;
} catch (ex) {
console.error('something happened trying to get pendingCommands: ', ex); // eslint-disable-line no-console
if (++errorCount > 10) {
console.error('pendingCommands threw 10 times, giving up'); // eslint-disable-line no-console
// NOTE: if we can't communicate with the window, we have to close it,
// since the user cannot.
closeWindow();
return;
}
}
commandPollTimer = setTimeout(pollForCommands, 25);
if (!cmd || !cmd.length) return;
// We found a command! Erase it, then act on it.
mvWindow.wrappedJSObject.resetCommands();
for (let i = 0; i < cmd.length; i++) {
let parsed;
try {
parsed = JSON.parse(cmd[i]);
webExtPort.postMessage({
content: 'msg-from-frontend',
data: parsed
});
} catch (ex) {
console.error('malformed command sent to addon: ', cmd[i], ex); // eslint-disable-line no-console
break;
}
}
}
whenReady(pollForCommands);
}
function makeDraggable() {
// Based on WindowDraggingElement usage in popup.xml
// https://dxr.mozilla.org/mozilla-central/source/toolkit/content/widgets/popup.xml#278-288
const draghandle = new DraggableElement(mvWindow);
draghandle.mouseDownCheck = () => { return true; };
// Update the saved position each time the draggable window is dropped.
// Listening for 'dragend' events doesn't work, so use 'mouseup' instead.
mvWindow.document.addEventListener('mouseup', sendLocation);
}
function destroy(isUnload) {
closeWindow();
if (isUnload) {
Services.obs.removeObserver(onWindowClosed, 'xul-window-destroyed');
Services.obs.removeObserver(closeRequested, 'browser-lastwindow-close-granted');
}
}
function minimize() {
mvWindow.resizeTo(DIMENSIONS.width, DIMENSIONS.minimizedHeight);
mvWindow.moveBy(0, DIMENSIONS.height - DIMENSIONS.minimizedHeight);
sendLocation();
}
function maximize() {
mvWindow.resizeTo(DIMENSIONS.width, DIMENSIONS.height);
mvWindow.moveBy(0, DIMENSIONS.minimizedHeight - DIMENSIONS.height);
sendLocation();
}
function sendLocation() {
webExtPort.postMessage({
content: 'position-changed',
data: {left: LOCATION.x = mvWindow.screenX, top: LOCATION.y = mvWindow.screenY}
});
}