|
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 "use strict"; |
|
5 |
|
6 module.metadata = { |
|
7 "stability": "deprecated" |
|
8 }; |
|
9 |
|
10 const { Worker } = require('./traits-worker'); |
|
11 const { Loader } = require('../content/loader'); |
|
12 const hiddenFrames = require('../frame/hidden-frame'); |
|
13 const { on, off } = require('../system/events'); |
|
14 const unload = require('../system/unload'); |
|
15 const { getDocShell } = require("../frame/utils"); |
|
16 const { ignoreWindow } = require('../private-browsing/utils'); |
|
17 |
|
18 // Everything coming from add-on's xpi considered an asset. |
|
19 const assetsURI = require('../self').data.url().replace(/data\/$/, ""); |
|
20 |
|
21 /** |
|
22 * This trait is layered on top of `Worker` and in contrast to symbiont |
|
23 * Worker constructor requires `content` option that represents content |
|
24 * that will be loaded in the provided frame, if frame is not provided |
|
25 * Worker will create hidden one. |
|
26 */ |
|
27 const Symbiont = Worker.resolve({ |
|
28 constructor: '_initWorker', |
|
29 destroy: '_workerDestroy' |
|
30 }).compose(Loader, { |
|
31 |
|
32 /** |
|
33 * The constructor requires all the options that are required by |
|
34 * `require('content').Worker` with the difference that the `frame` option |
|
35 * is optional. If `frame` is not provided, `contentURL` is expected. |
|
36 * @param {Object} options |
|
37 * @param {String} options.contentURL |
|
38 * URL of a content to load into `this._frame` and create worker for. |
|
39 * @param {Element} [options.frame] |
|
40 * iframe element that is used to load `options.contentURL` into. |
|
41 * if frame is not provided hidden iframe will be created. |
|
42 */ |
|
43 constructor: function Symbiont(options) { |
|
44 options = options || {}; |
|
45 |
|
46 if ('contentURL' in options) |
|
47 this.contentURL = options.contentURL; |
|
48 if ('contentScriptWhen' in options) |
|
49 this.contentScriptWhen = options.contentScriptWhen; |
|
50 if ('contentScriptOptions' in options) |
|
51 this.contentScriptOptions = options.contentScriptOptions; |
|
52 if ('contentScriptFile' in options) |
|
53 this.contentScriptFile = options.contentScriptFile; |
|
54 if ('contentScript' in options) |
|
55 this.contentScript = options.contentScript; |
|
56 if ('allow' in options) |
|
57 this.allow = options.allow; |
|
58 if ('onError' in options) |
|
59 this.on('error', options.onError); |
|
60 if ('onMessage' in options) |
|
61 this.on('message', options.onMessage); |
|
62 if ('frame' in options) { |
|
63 this._initFrame(options.frame); |
|
64 } |
|
65 else { |
|
66 let self = this; |
|
67 this._hiddenFrame = hiddenFrames.HiddenFrame({ |
|
68 onReady: function onFrame() { |
|
69 self._initFrame(this.element); |
|
70 }, |
|
71 onUnload: function onUnload() { |
|
72 // Bug 751211: Remove reference to _frame when hidden frame is |
|
73 // automatically removed on unload, otherwise we are going to face |
|
74 // "dead object" exception |
|
75 self.destroy(); |
|
76 } |
|
77 }); |
|
78 hiddenFrames.add(this._hiddenFrame); |
|
79 } |
|
80 |
|
81 unload.ensure(this._public, "destroy"); |
|
82 }, |
|
83 |
|
84 destroy: function destroy() { |
|
85 this._workerDestroy(); |
|
86 this._unregisterListener(); |
|
87 this._frame = null; |
|
88 if (this._hiddenFrame) { |
|
89 hiddenFrames.remove(this._hiddenFrame); |
|
90 this._hiddenFrame = null; |
|
91 } |
|
92 }, |
|
93 |
|
94 /** |
|
95 * XUL iframe or browser elements with attribute `type` being `content`. |
|
96 * Used to create `ContentSymbiont` from. |
|
97 * @type {nsIFrame|nsIBrowser} |
|
98 */ |
|
99 _frame: null, |
|
100 |
|
101 /** |
|
102 * Listener to the `'frameReady"` event (emitted when `iframe` is ready). |
|
103 * Removes listener, sets right permissions to the frame and loads content. |
|
104 */ |
|
105 _initFrame: function _initFrame(frame) { |
|
106 if (this._loadListener) |
|
107 this._unregisterListener(); |
|
108 |
|
109 this._frame = frame; |
|
110 |
|
111 if (getDocShell(frame)) { |
|
112 this._reallyInitFrame(frame); |
|
113 } |
|
114 else { |
|
115 if (this._waitForFrame) { |
|
116 off('content-document-global-created', this._waitForFrame); |
|
117 } |
|
118 this._waitForFrame = this.__waitForFrame.bind(this, frame); |
|
119 on('content-document-global-created', this._waitForFrame); |
|
120 } |
|
121 }, |
|
122 |
|
123 __waitForFrame: function _waitForFrame(frame, { subject: win }) { |
|
124 if (frame.contentWindow == win) { |
|
125 off('content-document-global-created', this._waitForFrame); |
|
126 delete this._waitForFrame; |
|
127 this._reallyInitFrame(frame); |
|
128 } |
|
129 }, |
|
130 |
|
131 _reallyInitFrame: function _reallyInitFrame(frame) { |
|
132 getDocShell(frame).allowJavascript = this.allow.script; |
|
133 frame.setAttribute("src", this._contentURL); |
|
134 |
|
135 // Inject `addon` object in document if we load a document from |
|
136 // one of our addon folder and if no content script are defined. bug 612726 |
|
137 let isDataResource = |
|
138 typeof this._contentURL == "string" && |
|
139 this._contentURL.indexOf(assetsURI) == 0; |
|
140 let hasContentScript = |
|
141 (Array.isArray(this.contentScript) ? this.contentScript.length > 0 |
|
142 : !!this.contentScript) || |
|
143 (Array.isArray(this.contentScriptFile) ? this.contentScriptFile.length > 0 |
|
144 : !!this.contentScriptFile); |
|
145 // If we have to inject `addon` we have to do it before document |
|
146 // script execution, so during `start`: |
|
147 this._injectInDocument = isDataResource && !hasContentScript; |
|
148 if (this._injectInDocument) |
|
149 this.contentScriptWhen = "start"; |
|
150 |
|
151 if ((frame.contentDocument.readyState == "complete" || |
|
152 (frame.contentDocument.readyState == "interactive" && |
|
153 this.contentScriptWhen != 'end' )) && |
|
154 frame.contentDocument.location == this._contentURL) { |
|
155 // In some cases src doesn't change and document is already ready |
|
156 // (for ex: when the user moves a widget while customizing toolbars.) |
|
157 this._onInit(); |
|
158 return; |
|
159 } |
|
160 |
|
161 let self = this; |
|
162 |
|
163 if ('start' == this.contentScriptWhen) { |
|
164 this._loadEvent = 'start'; |
|
165 on('document-element-inserted', |
|
166 this._loadListener = function onStart({ subject: doc }) { |
|
167 let window = doc.defaultView; |
|
168 |
|
169 if (ignoreWindow(window)) { |
|
170 return; |
|
171 } |
|
172 |
|
173 if (window && window == frame.contentWindow) { |
|
174 self._unregisterListener(); |
|
175 self._onInit(); |
|
176 } |
|
177 |
|
178 }); |
|
179 return; |
|
180 } |
|
181 |
|
182 let eventName = 'end' == this.contentScriptWhen ? 'load' : 'DOMContentLoaded'; |
|
183 let self = this; |
|
184 this._loadEvent = eventName; |
|
185 frame.addEventListener(eventName, |
|
186 this._loadListener = function _onReady(event) { |
|
187 |
|
188 if (event.target != frame.contentDocument) |
|
189 return; |
|
190 self._unregisterListener(); |
|
191 |
|
192 self._onInit(); |
|
193 |
|
194 }, true); |
|
195 |
|
196 }, |
|
197 |
|
198 /** |
|
199 * Unregister listener that watchs for document being ready to be injected. |
|
200 * This listener is registered in `Symbiont._initFrame`. |
|
201 */ |
|
202 _unregisterListener: function _unregisterListener() { |
|
203 if (this._waitForFrame) { |
|
204 off('content-document-global-created', this._waitForFrame); |
|
205 delete this._waitForFrame; |
|
206 } |
|
207 |
|
208 if (!this._loadListener) |
|
209 return; |
|
210 if (this._loadEvent == "start") { |
|
211 off('document-element-inserted', this._loadListener); |
|
212 } |
|
213 else { |
|
214 this._frame.removeEventListener(this._loadEvent, this._loadListener, |
|
215 true); |
|
216 } |
|
217 this._loadListener = null; |
|
218 }, |
|
219 |
|
220 /** |
|
221 * Called by Symbiont itself when the frame is ready to load |
|
222 * content scripts according to contentScriptWhen. Overloaded by Panel. |
|
223 */ |
|
224 _onInit: function () { |
|
225 this._initWorker({ window: this._frame.contentWindow }); |
|
226 } |
|
227 |
|
228 }); |
|
229 exports.Symbiont = Symbiont; |