|
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 /** |
|
6 * |
|
7 * `deprecated/traits-worker` was previously `content/worker` and kept |
|
8 * only due to `deprecated/symbiont` using it, which is necessary for |
|
9 * `widget`, until that reaches deprecation EOL. |
|
10 * |
|
11 */ |
|
12 |
|
13 "use strict"; |
|
14 |
|
15 module.metadata = { |
|
16 "stability": "deprecated" |
|
17 }; |
|
18 |
|
19 const { Trait } = require('./traits'); |
|
20 const { EventEmitter, EventEmitterTrait } = require('./events'); |
|
21 const { Ci, Cu, Cc } = require('chrome'); |
|
22 const timer = require('../timers'); |
|
23 const { URL } = require('../url'); |
|
24 const unload = require('../system/unload'); |
|
25 const observers = require('../system/events'); |
|
26 const { Cortex } = require('./cortex'); |
|
27 const { sandbox, evaluate, load } = require("../loader/sandbox"); |
|
28 const { merge } = require('../util/object'); |
|
29 const { getInnerId } = require("../window/utils"); |
|
30 const { getTabForWindow } = require('../tabs/helpers'); |
|
31 const { getTabForContentWindow } = require('../tabs/utils'); |
|
32 |
|
33 /* Trick the linker in order to ensure shipping these files in the XPI. |
|
34 require('../content/content-worker.js'); |
|
35 Then, retrieve URL of these files in the XPI: |
|
36 */ |
|
37 let prefix = module.uri.split('deprecated/traits-worker.js')[0]; |
|
38 const CONTENT_WORKER_URL = prefix + 'content/content-worker.js'; |
|
39 |
|
40 // Fetch additional list of domains to authorize access to for each content |
|
41 // script. It is stored in manifest `metadata` field which contains |
|
42 // package.json data. This list is originaly defined by authors in |
|
43 // `permissions` attribute of their package.json addon file. |
|
44 const permissions = require('@loader/options').metadata['permissions'] || {}; |
|
45 const EXPANDED_PRINCIPALS = permissions['cross-domain-content'] || []; |
|
46 |
|
47 const JS_VERSION = '1.8'; |
|
48 |
|
49 const ERR_DESTROYED = |
|
50 "Couldn't find the worker to receive this message. " + |
|
51 "The script may not be initialized yet, or may already have been unloaded."; |
|
52 |
|
53 const ERR_FROZEN = "The page is currently hidden and can no longer be used " + |
|
54 "until it is visible again."; |
|
55 |
|
56 |
|
57 const WorkerSandbox = EventEmitter.compose({ |
|
58 |
|
59 /** |
|
60 * Emit a message to the worker content sandbox |
|
61 */ |
|
62 emit: function emit() { |
|
63 // First ensure having a regular array |
|
64 // (otherwise, `arguments` would be mapped to an object by `stringify`) |
|
65 let array = Array.slice(arguments); |
|
66 // JSON.stringify is buggy with cross-sandbox values, |
|
67 // it may return "{}" on functions. Use a replacer to match them correctly. |
|
68 function replacer(k, v) { |
|
69 return typeof v === "function" ? undefined : v; |
|
70 } |
|
71 // Ensure having an asynchronous behavior |
|
72 let self = this; |
|
73 timer.setTimeout(function () { |
|
74 self._emitToContent(JSON.stringify(array, replacer)); |
|
75 }, 0); |
|
76 }, |
|
77 |
|
78 /** |
|
79 * Synchronous version of `emit`. |
|
80 * /!\ Should only be used when it is strictly mandatory /!\ |
|
81 * Doesn't ensure passing only JSON values. |
|
82 * Mainly used by context-menu in order to avoid breaking it. |
|
83 */ |
|
84 emitSync: function emitSync() { |
|
85 let args = Array.slice(arguments); |
|
86 return this._emitToContent(args); |
|
87 }, |
|
88 |
|
89 /** |
|
90 * Tells if content script has at least one listener registered for one event, |
|
91 * through `self.on('xxx', ...)`. |
|
92 * /!\ Shouldn't be used. Implemented to avoid breaking context-menu API. |
|
93 */ |
|
94 hasListenerFor: function hasListenerFor(name) { |
|
95 return this._hasListenerFor(name); |
|
96 }, |
|
97 |
|
98 /** |
|
99 * Method called by the worker sandbox when it needs to send a message |
|
100 */ |
|
101 _onContentEvent: function onContentEvent(args) { |
|
102 // As `emit`, we ensure having an asynchronous behavior |
|
103 let self = this; |
|
104 timer.setTimeout(function () { |
|
105 // We emit event to chrome/addon listeners |
|
106 self._emit.apply(self, JSON.parse(args)); |
|
107 }, 0); |
|
108 }, |
|
109 |
|
110 /** |
|
111 * Configures sandbox and loads content scripts into it. |
|
112 * @param {Worker} worker |
|
113 * content worker |
|
114 */ |
|
115 constructor: function WorkerSandbox(worker) { |
|
116 this._addonWorker = worker; |
|
117 |
|
118 // Ensure that `emit` has always the right `this` |
|
119 this.emit = this.emit.bind(this); |
|
120 this.emitSync = this.emitSync.bind(this); |
|
121 |
|
122 // We receive a wrapped window, that may be an xraywrapper if it's content |
|
123 let window = worker._window; |
|
124 let proto = window; |
|
125 |
|
126 // Eventually use expanded principal sandbox feature, if some are given. |
|
127 // |
|
128 // But prevent it when the Worker isn't used for a content script but for |
|
129 // injecting `addon` object into a Panel, Widget, ... scope. |
|
130 // That's because: |
|
131 // 1/ It is useless to use multiple domains as the worker is only used |
|
132 // to communicate with the addon, |
|
133 // 2/ By using it it would prevent the document to have access to any JS |
|
134 // value of the worker. As JS values coming from multiple domain principals |
|
135 // can't be accessed by "mono-principals" (principal with only one domain). |
|
136 // Even if this principal is for a domain that is specified in the multiple |
|
137 // domain principal. |
|
138 let principals = window; |
|
139 let wantGlobalProperties = [] |
|
140 if (EXPANDED_PRINCIPALS.length > 0 && !worker._injectInDocument) { |
|
141 principals = EXPANDED_PRINCIPALS.concat(window); |
|
142 // We have to replace XHR constructor of the content document |
|
143 // with a custom cross origin one, automagically added by platform code: |
|
144 delete proto.XMLHttpRequest; |
|
145 wantGlobalProperties.push("XMLHttpRequest"); |
|
146 } |
|
147 |
|
148 // Instantiate trusted code in another Sandbox in order to prevent content |
|
149 // script from messing with standard classes used by proxy and API code. |
|
150 let apiSandbox = sandbox(principals, { wantXrays: true, sameZoneAs: window }); |
|
151 apiSandbox.console = console; |
|
152 |
|
153 // Create the sandbox and bind it to window in order for content scripts to |
|
154 // have access to all standard globals (window, document, ...) |
|
155 let content = this._sandbox = sandbox(principals, { |
|
156 sandboxPrototype: proto, |
|
157 wantXrays: true, |
|
158 wantGlobalProperties: wantGlobalProperties, |
|
159 sameZoneAs: window, |
|
160 metadata: { |
|
161 SDKContentScript: true, |
|
162 'inner-window-id': getInnerId(window) |
|
163 } |
|
164 }); |
|
165 // We have to ensure that window.top and window.parent are the exact same |
|
166 // object than window object, i.e. the sandbox global object. But not |
|
167 // always, in case of iframes, top and parent are another window object. |
|
168 let top = window.top === window ? content : content.top; |
|
169 let parent = window.parent === window ? content : content.parent; |
|
170 merge(content, { |
|
171 // We need "this === window === top" to be true in toplevel scope: |
|
172 get window() content, |
|
173 get top() top, |
|
174 get parent() parent, |
|
175 // Use the Greasemonkey naming convention to provide access to the |
|
176 // unwrapped window object so the content script can access document |
|
177 // JavaScript values. |
|
178 // NOTE: this functionality is experimental and may change or go away |
|
179 // at any time! |
|
180 get unsafeWindow() window.wrappedJSObject |
|
181 }); |
|
182 |
|
183 // Load trusted code that will inject content script API. |
|
184 // We need to expose JS objects defined in same principal in order to |
|
185 // avoid having any kind of wrapper. |
|
186 load(apiSandbox, CONTENT_WORKER_URL); |
|
187 |
|
188 // prepare a clean `self.options` |
|
189 let options = 'contentScriptOptions' in worker ? |
|
190 JSON.stringify( worker.contentScriptOptions ) : |
|
191 undefined; |
|
192 |
|
193 // Then call `inject` method and communicate with this script |
|
194 // by trading two methods that allow to send events to the other side: |
|
195 // - `onEvent` called by content script |
|
196 // - `result.emitToContent` called by addon script |
|
197 // Bug 758203: We have to explicitely define `__exposedProps__` in order |
|
198 // to allow access to these chrome object attributes from this sandbox with |
|
199 // content priviledges |
|
200 // https://developer.mozilla.org/en/XPConnect_wrappers#Other_security_wrappers |
|
201 let chromeAPI = { |
|
202 timers: { |
|
203 setTimeout: timer.setTimeout, |
|
204 setInterval: timer.setInterval, |
|
205 clearTimeout: timer.clearTimeout, |
|
206 clearInterval: timer.clearInterval, |
|
207 __exposedProps__: { |
|
208 setTimeout: 'r', |
|
209 setInterval: 'r', |
|
210 clearTimeout: 'r', |
|
211 clearInterval: 'r' |
|
212 } |
|
213 }, |
|
214 sandbox: { |
|
215 evaluate: evaluate, |
|
216 __exposedProps__: { |
|
217 evaluate: 'r', |
|
218 } |
|
219 }, |
|
220 __exposedProps__: { |
|
221 timers: 'r', |
|
222 sandbox: 'r', |
|
223 } |
|
224 }; |
|
225 let onEvent = this._onContentEvent.bind(this); |
|
226 // `ContentWorker` is defined in CONTENT_WORKER_URL file |
|
227 let result = apiSandbox.ContentWorker.inject(content, chromeAPI, onEvent, options); |
|
228 this._emitToContent = result.emitToContent; |
|
229 this._hasListenerFor = result.hasListenerFor; |
|
230 |
|
231 // Handle messages send by this script: |
|
232 let self = this; |
|
233 // console.xxx calls |
|
234 this.on("console", function consoleListener(kind) { |
|
235 console[kind].apply(console, Array.slice(arguments, 1)); |
|
236 }); |
|
237 |
|
238 // self.postMessage calls |
|
239 this.on("message", function postMessage(data) { |
|
240 // destroyed? |
|
241 if (self._addonWorker) |
|
242 self._addonWorker._emit('message', data); |
|
243 }); |
|
244 |
|
245 // self.port.emit calls |
|
246 this.on("event", function portEmit(name, args) { |
|
247 // destroyed? |
|
248 if (self._addonWorker) |
|
249 self._addonWorker._onContentScriptEvent.apply(self._addonWorker, arguments); |
|
250 }); |
|
251 |
|
252 // unwrap, recreate and propagate async Errors thrown from content-script |
|
253 this.on("error", function onError({instanceOfError, value}) { |
|
254 if (self._addonWorker) { |
|
255 let error = value; |
|
256 if (instanceOfError) { |
|
257 error = new Error(value.message, value.fileName, value.lineNumber); |
|
258 error.stack = value.stack; |
|
259 error.name = value.name; |
|
260 } |
|
261 self._addonWorker._emit('error', error); |
|
262 } |
|
263 }); |
|
264 |
|
265 // Inject `addon` global into target document if document is trusted, |
|
266 // `addon` in document is equivalent to `self` in content script. |
|
267 if (worker._injectInDocument) { |
|
268 let win = window.wrappedJSObject ? window.wrappedJSObject : window; |
|
269 Object.defineProperty(win, "addon", { |
|
270 value: content.self |
|
271 } |
|
272 ); |
|
273 } |
|
274 |
|
275 // Inject our `console` into target document if worker doesn't have a tab |
|
276 // (e.g Panel, PageWorker, Widget). |
|
277 // `worker.tab` can't be used because bug 804935. |
|
278 if (!getTabForContentWindow(window)) { |
|
279 let win = window.wrappedJSObject ? window.wrappedJSObject : window; |
|
280 |
|
281 // export our chrome console to content window as described here: |
|
282 // https://developer.mozilla.org/en-US/docs/Components.utils.createObjectIn |
|
283 let con = Cu.createObjectIn(win); |
|
284 |
|
285 let genPropDesc = function genPropDesc(fun) { |
|
286 return { enumerable: true, configurable: true, writable: true, |
|
287 value: console[fun] }; |
|
288 } |
|
289 |
|
290 const properties = { |
|
291 log: genPropDesc('log'), |
|
292 info: genPropDesc('info'), |
|
293 warn: genPropDesc('warn'), |
|
294 error: genPropDesc('error'), |
|
295 debug: genPropDesc('debug'), |
|
296 trace: genPropDesc('trace'), |
|
297 dir: genPropDesc('dir'), |
|
298 group: genPropDesc('group'), |
|
299 groupCollapsed: genPropDesc('groupCollapsed'), |
|
300 groupEnd: genPropDesc('groupEnd'), |
|
301 time: genPropDesc('time'), |
|
302 timeEnd: genPropDesc('timeEnd'), |
|
303 profile: genPropDesc('profile'), |
|
304 profileEnd: genPropDesc('profileEnd'), |
|
305 __noSuchMethod__: { enumerable: true, configurable: true, writable: true, |
|
306 value: function() {} } |
|
307 }; |
|
308 |
|
309 Object.defineProperties(con, properties); |
|
310 Cu.makeObjectPropsNormal(con); |
|
311 |
|
312 win.console = con; |
|
313 }; |
|
314 |
|
315 // The order of `contentScriptFile` and `contentScript` evaluation is |
|
316 // intentional, so programs can load libraries like jQuery from script URLs |
|
317 // and use them in scripts. |
|
318 let contentScriptFile = ('contentScriptFile' in worker) ? worker.contentScriptFile |
|
319 : null, |
|
320 contentScript = ('contentScript' in worker) ? worker.contentScript : null; |
|
321 |
|
322 if (contentScriptFile) { |
|
323 if (Array.isArray(contentScriptFile)) |
|
324 this._importScripts.apply(this, contentScriptFile); |
|
325 else |
|
326 this._importScripts(contentScriptFile); |
|
327 } |
|
328 if (contentScript) { |
|
329 this._evaluate( |
|
330 Array.isArray(contentScript) ? contentScript.join(';\n') : contentScript |
|
331 ); |
|
332 } |
|
333 }, |
|
334 destroy: function destroy() { |
|
335 this.emitSync("detach"); |
|
336 this._sandbox = null; |
|
337 this._addonWorker = null; |
|
338 }, |
|
339 |
|
340 /** |
|
341 * JavaScript sandbox where all the content scripts are evaluated. |
|
342 * {Sandbox} |
|
343 */ |
|
344 _sandbox: null, |
|
345 |
|
346 /** |
|
347 * Reference to the addon side of the worker. |
|
348 * @type {Worker} |
|
349 */ |
|
350 _addonWorker: null, |
|
351 |
|
352 /** |
|
353 * Evaluates code in the sandbox. |
|
354 * @param {String} code |
|
355 * JavaScript source to evaluate. |
|
356 * @param {String} [filename='javascript:' + code] |
|
357 * Name of the file |
|
358 */ |
|
359 _evaluate: function(code, filename) { |
|
360 try { |
|
361 evaluate(this._sandbox, code, filename || 'javascript:' + code); |
|
362 } |
|
363 catch(e) { |
|
364 this._addonWorker._emit('error', e); |
|
365 } |
|
366 }, |
|
367 /** |
|
368 * Imports scripts to the sandbox by reading files under urls and |
|
369 * evaluating its source. If exception occurs during evaluation |
|
370 * `"error"` event is emitted on the worker. |
|
371 * This is actually an analog to the `importScript` method in web |
|
372 * workers but in our case it's not exposed even though content |
|
373 * scripts may be able to do it synchronously since IO operation |
|
374 * takes place in the UI process. |
|
375 */ |
|
376 _importScripts: function _importScripts(url) { |
|
377 let urls = Array.slice(arguments, 0); |
|
378 for each (let contentScriptFile in urls) { |
|
379 try { |
|
380 let uri = URL(contentScriptFile); |
|
381 if (uri.scheme === 'resource') |
|
382 load(this._sandbox, String(uri)); |
|
383 else |
|
384 throw Error("Unsupported `contentScriptFile` url: " + String(uri)); |
|
385 } |
|
386 catch(e) { |
|
387 this._addonWorker._emit('error', e); |
|
388 } |
|
389 } |
|
390 } |
|
391 }); |
|
392 |
|
393 /** |
|
394 * Message-passing facility for communication between code running |
|
395 * in the content and add-on process. |
|
396 * @see https://addons.mozilla.org/en-US/developers/docs/sdk/latest/modules/sdk/content/worker.html |
|
397 */ |
|
398 const Worker = EventEmitter.compose({ |
|
399 on: Trait.required, |
|
400 _removeAllListeners: Trait.required, |
|
401 |
|
402 // List of messages fired before worker is initialized |
|
403 get _earlyEvents() { |
|
404 delete this._earlyEvents; |
|
405 this._earlyEvents = []; |
|
406 return this._earlyEvents; |
|
407 }, |
|
408 |
|
409 /** |
|
410 * Sends a message to the worker's global scope. Method takes single |
|
411 * argument, which represents data to be sent to the worker. The data may |
|
412 * be any primitive type value or `JSON`. Call of this method asynchronously |
|
413 * emits `message` event with data value in the global scope of this |
|
414 * symbiont. |
|
415 * |
|
416 * `message` event listeners can be set either by calling |
|
417 * `self.on` with a first argument string `"message"` or by |
|
418 * implementing `onMessage` function in the global scope of this worker. |
|
419 * @param {Number|String|JSON} data |
|
420 */ |
|
421 postMessage: function (data) { |
|
422 let args = ['message'].concat(Array.slice(arguments)); |
|
423 if (!this._inited) { |
|
424 this._earlyEvents.push(args); |
|
425 return; |
|
426 } |
|
427 processMessage.apply(this, args); |
|
428 }, |
|
429 |
|
430 /** |
|
431 * EventEmitter, that behaves (calls listeners) asynchronously. |
|
432 * A way to send customized messages to / from the worker. |
|
433 * Events from in the worker can be observed / emitted via |
|
434 * worker.on / worker.emit. |
|
435 */ |
|
436 get port() { |
|
437 // We generate dynamically this attribute as it needs to be accessible |
|
438 // before Worker.constructor gets called. (For ex: Panel) |
|
439 |
|
440 // create an event emitter that receive and send events from/to the worker |
|
441 this._port = EventEmitterTrait.create({ |
|
442 emit: this._emitEventToContent.bind(this) |
|
443 }); |
|
444 |
|
445 // expose wrapped port, that exposes only public properties: |
|
446 // We need to destroy this getter in order to be able to set the |
|
447 // final value. We need to update only public port attribute as we never |
|
448 // try to access port attribute from private API. |
|
449 delete this._public.port; |
|
450 this._public.port = Cortex(this._port); |
|
451 // Replicate public port to the private object |
|
452 delete this.port; |
|
453 this.port = this._public.port; |
|
454 |
|
455 return this._port; |
|
456 }, |
|
457 |
|
458 /** |
|
459 * Same object than this.port but private API. |
|
460 * Allow access to _emit, in order to send event to port. |
|
461 */ |
|
462 _port: null, |
|
463 |
|
464 /** |
|
465 * Emit a custom event to the content script, |
|
466 * i.e. emit this event on `self.port` |
|
467 */ |
|
468 _emitEventToContent: function () { |
|
469 let args = ['event'].concat(Array.slice(arguments)); |
|
470 if (!this._inited) { |
|
471 this._earlyEvents.push(args); |
|
472 return; |
|
473 } |
|
474 processMessage.apply(this, args); |
|
475 }, |
|
476 |
|
477 // Is worker connected to the content worker sandbox ? |
|
478 _inited: false, |
|
479 |
|
480 // Is worker being frozen? i.e related document is frozen in bfcache. |
|
481 // Content script should not be reachable if frozen. |
|
482 _frozen: true, |
|
483 |
|
484 constructor: function Worker(options) { |
|
485 options = options || {}; |
|
486 |
|
487 if ('contentScriptFile' in options) |
|
488 this.contentScriptFile = options.contentScriptFile; |
|
489 if ('contentScriptOptions' in options) |
|
490 this.contentScriptOptions = options.contentScriptOptions; |
|
491 if ('contentScript' in options) |
|
492 this.contentScript = options.contentScript; |
|
493 |
|
494 this._setListeners(options); |
|
495 |
|
496 unload.ensure(this._public, "destroy"); |
|
497 |
|
498 // Ensure that worker._port is initialized for contentWorker to be able |
|
499 // to send events during worker initialization. |
|
500 this.port; |
|
501 |
|
502 this._documentUnload = this._documentUnload.bind(this); |
|
503 this._pageShow = this._pageShow.bind(this); |
|
504 this._pageHide = this._pageHide.bind(this); |
|
505 |
|
506 if ("window" in options) this._attach(options.window); |
|
507 }, |
|
508 |
|
509 _setListeners: function(options) { |
|
510 if ('onError' in options) |
|
511 this.on('error', options.onError); |
|
512 if ('onMessage' in options) |
|
513 this.on('message', options.onMessage); |
|
514 if ('onDetach' in options) |
|
515 this.on('detach', options.onDetach); |
|
516 }, |
|
517 |
|
518 _attach: function(window) { |
|
519 this._window = window; |
|
520 // Track document unload to destroy this worker. |
|
521 // We can't watch for unload event on page's window object as it |
|
522 // prevents bfcache from working: |
|
523 // https://developer.mozilla.org/En/Working_with_BFCache |
|
524 this._windowID = getInnerId(this._window); |
|
525 observers.on("inner-window-destroyed", this._documentUnload); |
|
526 |
|
527 // Listen to pagehide event in order to freeze the content script |
|
528 // while the document is frozen in bfcache: |
|
529 this._window.addEventListener("pageshow", this._pageShow, true); |
|
530 this._window.addEventListener("pagehide", this._pageHide, true); |
|
531 |
|
532 // will set this._contentWorker pointing to the private API: |
|
533 this._contentWorker = WorkerSandbox(this); |
|
534 |
|
535 // Mainly enable worker.port.emit to send event to the content worker |
|
536 this._inited = true; |
|
537 this._frozen = false; |
|
538 |
|
539 // Process all events and messages that were fired before the |
|
540 // worker was initialized. |
|
541 this._earlyEvents.forEach((function (args) { |
|
542 processMessage.apply(this, args); |
|
543 }).bind(this)); |
|
544 }, |
|
545 |
|
546 _documentUnload: function _documentUnload({ subject, data }) { |
|
547 let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data; |
|
548 if (innerWinID != this._windowID) return false; |
|
549 this._workerCleanup(); |
|
550 return true; |
|
551 }, |
|
552 |
|
553 _pageShow: function _pageShow() { |
|
554 this._contentWorker.emitSync("pageshow"); |
|
555 this._emit("pageshow"); |
|
556 this._frozen = false; |
|
557 }, |
|
558 |
|
559 _pageHide: function _pageHide() { |
|
560 this._contentWorker.emitSync("pagehide"); |
|
561 this._emit("pagehide"); |
|
562 this._frozen = true; |
|
563 }, |
|
564 |
|
565 get url() { |
|
566 // this._window will be null after detach |
|
567 return this._window ? this._window.document.location.href : null; |
|
568 }, |
|
569 |
|
570 get tab() { |
|
571 // this._window will be null after detach |
|
572 if (this._window) |
|
573 return getTabForWindow(this._window); |
|
574 return null; |
|
575 }, |
|
576 |
|
577 /** |
|
578 * Tells content worker to unload itself and |
|
579 * removes all the references from itself. |
|
580 */ |
|
581 destroy: function destroy() { |
|
582 this._workerCleanup(); |
|
583 this._inited = true; |
|
584 this._removeAllListeners(); |
|
585 }, |
|
586 |
|
587 /** |
|
588 * Remove all internal references to the attached document |
|
589 * Tells _port to unload itself and removes all the references from itself. |
|
590 */ |
|
591 _workerCleanup: function _workerCleanup() { |
|
592 // maybe unloaded before content side is created |
|
593 // As Symbiont call worker.constructor on document load |
|
594 if (this._contentWorker) |
|
595 this._contentWorker.destroy(); |
|
596 this._contentWorker = null; |
|
597 if (this._window) { |
|
598 this._window.removeEventListener("pageshow", this._pageShow, true); |
|
599 this._window.removeEventListener("pagehide", this._pageHide, true); |
|
600 } |
|
601 this._window = null; |
|
602 // This method may be called multiple times, |
|
603 // avoid dispatching `detach` event more than once |
|
604 if (this._windowID) { |
|
605 this._windowID = null; |
|
606 observers.off("inner-window-destroyed", this._documentUnload); |
|
607 this._earlyEvents.length = 0; |
|
608 this._emit("detach"); |
|
609 } |
|
610 this._inited = false; |
|
611 }, |
|
612 |
|
613 /** |
|
614 * Receive an event from the content script that need to be sent to |
|
615 * worker.port. Provide a way for composed object to catch all events. |
|
616 */ |
|
617 _onContentScriptEvent: function _onContentScriptEvent() { |
|
618 this._port._emit.apply(this._port, arguments); |
|
619 }, |
|
620 |
|
621 /** |
|
622 * Reference to the content side of the worker. |
|
623 * @type {WorkerGlobalScope} |
|
624 */ |
|
625 _contentWorker: null, |
|
626 |
|
627 /** |
|
628 * Reference to the window that is accessible from |
|
629 * the content scripts. |
|
630 * @type {Object} |
|
631 */ |
|
632 _window: null, |
|
633 |
|
634 /** |
|
635 * Flag to enable `addon` object injection in document. (bug 612726) |
|
636 * @type {Boolean} |
|
637 */ |
|
638 _injectInDocument: false |
|
639 }); |
|
640 |
|
641 /** |
|
642 * Fired from postMessage and _emitEventToContent, or from the _earlyMessage |
|
643 * queue when fired before the content is loaded. Sends arguments to |
|
644 * contentWorker if able |
|
645 */ |
|
646 |
|
647 function processMessage () { |
|
648 if (!this._contentWorker) |
|
649 throw new Error(ERR_DESTROYED); |
|
650 if (this._frozen) |
|
651 throw new Error(ERR_FROZEN); |
|
652 |
|
653 this._contentWorker.emit.apply(null, Array.slice(arguments)); |
|
654 } |
|
655 |
|
656 exports.Worker = Worker; |