|
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 let Cc = Components.classes; |
|
6 let Ci = Components.interfaces; |
|
7 let Cu = Components.utils; |
|
8 |
|
9 Cu.import("resource://gre/modules/Services.jsm"); |
|
10 Cu.import('resource://gre/modules/XPCOMUtils.jsm'); |
|
11 Cu.import("resource://gre/modules/RemoteAddonsChild.jsm"); |
|
12 Cu.import("resource://gre/modules/Timer.jsm"); |
|
13 |
|
14 let SyncHandler = { |
|
15 init: function() { |
|
16 sendAsyncMessage("SetSyncHandler", {}, {handler: this}); |
|
17 }, |
|
18 |
|
19 getFocusedElementAndWindow: function() { |
|
20 let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); |
|
21 |
|
22 let focusedWindow = {}; |
|
23 let elt = fm.getFocusedElementForWindow(content, true, focusedWindow); |
|
24 return [elt, focusedWindow.value]; |
|
25 }, |
|
26 }; |
|
27 |
|
28 SyncHandler.init(); |
|
29 |
|
30 let WebProgressListener = { |
|
31 init: function() { |
|
32 let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) |
|
33 .getInterface(Ci.nsIWebProgress); |
|
34 webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL); |
|
35 }, |
|
36 |
|
37 _requestSpec: function (aRequest) { |
|
38 if (!aRequest || !(aRequest instanceof Ci.nsIChannel)) |
|
39 return null; |
|
40 return aRequest.QueryInterface(Ci.nsIChannel).URI.spec; |
|
41 }, |
|
42 |
|
43 _setupJSON: function setupJSON(aWebProgress, aRequest) { |
|
44 return { |
|
45 isTopLevel: aWebProgress.isTopLevel, |
|
46 isLoadingDocument: aWebProgress.isLoadingDocument, |
|
47 requestURI: this._requestSpec(aRequest), |
|
48 loadType: aWebProgress.loadType, |
|
49 documentContentType: content.document && content.document.contentType |
|
50 }; |
|
51 }, |
|
52 |
|
53 _setupObjects: function setupObjects(aWebProgress) { |
|
54 return { |
|
55 contentWindow: content, |
|
56 // DOMWindow is not necessarily the content-window with subframes. |
|
57 DOMWindow: aWebProgress.DOMWindow |
|
58 }; |
|
59 }, |
|
60 |
|
61 onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { |
|
62 let json = this._setupJSON(aWebProgress, aRequest); |
|
63 let objects = this._setupObjects(aWebProgress); |
|
64 |
|
65 json.stateFlags = aStateFlags; |
|
66 json.status = aStatus; |
|
67 |
|
68 sendAsyncMessage("Content:StateChange", json, objects); |
|
69 }, |
|
70 |
|
71 onProgressChange: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) { |
|
72 }, |
|
73 |
|
74 onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) { |
|
75 let json = this._setupJSON(aWebProgress, aRequest); |
|
76 let objects = this._setupObjects(aWebProgress); |
|
77 |
|
78 json.location = aLocationURI ? aLocationURI.spec : ""; |
|
79 json.flags = aFlags; |
|
80 |
|
81 if (json.isTopLevel) { |
|
82 json.canGoBack = docShell.canGoBack; |
|
83 json.canGoForward = docShell.canGoForward; |
|
84 json.documentURI = content.document.documentURIObject.spec; |
|
85 json.charset = content.document.characterSet; |
|
86 } |
|
87 |
|
88 sendAsyncMessage("Content:LocationChange", json, objects); |
|
89 }, |
|
90 |
|
91 onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) { |
|
92 let json = this._setupJSON(aWebProgress, aRequest); |
|
93 let objects = this._setupObjects(aWebProgress); |
|
94 |
|
95 json.status = aStatus; |
|
96 json.message = aMessage; |
|
97 |
|
98 sendAsyncMessage("Content:StatusChange", json, objects); |
|
99 }, |
|
100 |
|
101 onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) { |
|
102 let json = this._setupJSON(aWebProgress, aRequest); |
|
103 let objects = this._setupObjects(aWebProgress); |
|
104 |
|
105 json.state = aState; |
|
106 json.status = SecurityUI.getSSLStatusAsString(); |
|
107 |
|
108 sendAsyncMessage("Content:SecurityChange", json, objects); |
|
109 }, |
|
110 |
|
111 QueryInterface: function QueryInterface(aIID) { |
|
112 if (aIID.equals(Ci.nsIWebProgressListener) || |
|
113 aIID.equals(Ci.nsISupportsWeakReference) || |
|
114 aIID.equals(Ci.nsISupports)) { |
|
115 return this; |
|
116 } |
|
117 |
|
118 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
119 } |
|
120 }; |
|
121 |
|
122 WebProgressListener.init(); |
|
123 |
|
124 let WebNavigation = { |
|
125 _webNavigation: docShell.QueryInterface(Ci.nsIWebNavigation), |
|
126 |
|
127 init: function() { |
|
128 addMessageListener("WebNavigation:GoBack", this); |
|
129 addMessageListener("WebNavigation:GoForward", this); |
|
130 addMessageListener("WebNavigation:GotoIndex", this); |
|
131 addMessageListener("WebNavigation:LoadURI", this); |
|
132 addMessageListener("WebNavigation:Reload", this); |
|
133 addMessageListener("WebNavigation:Stop", this); |
|
134 |
|
135 // Send a CPOW for the sessionHistory object. |
|
136 let history = this._webNavigation.sessionHistory; |
|
137 sendAsyncMessage("WebNavigation:setHistory", {}, {history: history}); |
|
138 }, |
|
139 |
|
140 receiveMessage: function(message) { |
|
141 switch (message.name) { |
|
142 case "WebNavigation:GoBack": |
|
143 this.goBack(); |
|
144 break; |
|
145 case "WebNavigation:GoForward": |
|
146 this.goForward(); |
|
147 break; |
|
148 case "WebNavigation:GotoIndex": |
|
149 this.gotoIndex(message.data.index); |
|
150 break; |
|
151 case "WebNavigation:LoadURI": |
|
152 this.loadURI(message.data.uri, message.data.flags); |
|
153 break; |
|
154 case "WebNavigation:Reload": |
|
155 this.reload(message.data.flags); |
|
156 break; |
|
157 case "WebNavigation:Stop": |
|
158 this.stop(message.data.flags); |
|
159 break; |
|
160 } |
|
161 }, |
|
162 |
|
163 goBack: function() { |
|
164 if (this._webNavigation.canGoBack) |
|
165 this._webNavigation.goBack(); |
|
166 }, |
|
167 |
|
168 goForward: function() { |
|
169 if (this._webNavigation.canGoForward) |
|
170 this._webNavigation.goForward(); |
|
171 }, |
|
172 |
|
173 gotoIndex: function(index) { |
|
174 this._webNavigation.gotoIndex(index); |
|
175 }, |
|
176 |
|
177 loadURI: function(uri, flags) { |
|
178 this._webNavigation.loadURI(uri, flags, null, null, null); |
|
179 }, |
|
180 |
|
181 reload: function(flags) { |
|
182 this._webNavigation.reload(flags); |
|
183 }, |
|
184 |
|
185 stop: function(flags) { |
|
186 this._webNavigation.stop(flags); |
|
187 } |
|
188 }; |
|
189 |
|
190 WebNavigation.init(); |
|
191 |
|
192 let SecurityUI = { |
|
193 getSSLStatusAsString: function() { |
|
194 let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus; |
|
195 |
|
196 if (status) { |
|
197 let helper = Cc["@mozilla.org/network/serialization-helper;1"] |
|
198 .getService(Ci.nsISerializationHelper); |
|
199 |
|
200 status.QueryInterface(Ci.nsISerializable); |
|
201 return helper.serializeToString(status); |
|
202 } |
|
203 |
|
204 return null; |
|
205 } |
|
206 }; |
|
207 |
|
208 let ControllerCommands = { |
|
209 init: function () { |
|
210 addMessageListener("ControllerCommands:Do", this); |
|
211 }, |
|
212 |
|
213 receiveMessage: function(message) { |
|
214 switch(message.name) { |
|
215 case "ControllerCommands:Do": |
|
216 if (docShell.isCommandEnabled(message.data)) |
|
217 docShell.doCommand(message.data); |
|
218 break; |
|
219 } |
|
220 } |
|
221 } |
|
222 |
|
223 ControllerCommands.init() |
|
224 |
|
225 addEventListener("DOMTitleChanged", function (aEvent) { |
|
226 let document = content.document; |
|
227 switch (aEvent.type) { |
|
228 case "DOMTitleChanged": |
|
229 if (!aEvent.isTrusted || aEvent.target.defaultView != content) |
|
230 return; |
|
231 |
|
232 sendAsyncMessage("DOMTitleChanged", { title: document.title }); |
|
233 break; |
|
234 } |
|
235 }, false); |
|
236 |
|
237 addEventListener("DOMWindowClose", function (aEvent) { |
|
238 if (!aEvent.isTrusted) |
|
239 return; |
|
240 sendAsyncMessage("DOMWindowClose"); |
|
241 aEvent.preventDefault(); |
|
242 }, false); |
|
243 |
|
244 addEventListener("ImageContentLoaded", function (aEvent) { |
|
245 if (content.document instanceof Ci.nsIImageDocument) { |
|
246 let req = content.document.imageRequest; |
|
247 if (!req.image) |
|
248 return; |
|
249 sendAsyncMessage("ImageDocumentLoaded", { width: req.image.width, |
|
250 height: req.image.height }); |
|
251 } |
|
252 }, false); |
|
253 |
|
254 let DocumentObserver = { |
|
255 init: function() { |
|
256 Services.obs.addObserver(this, "document-element-inserted", false); |
|
257 addEventListener("unload", () => { |
|
258 Services.obs.removeObserver(this, "document-element-inserted"); |
|
259 }); |
|
260 }, |
|
261 |
|
262 observe: function(aSubject, aTopic, aData) { |
|
263 if (aSubject == content.document) { |
|
264 sendAsyncMessage("DocumentInserted", {synthetic: aSubject.mozSyntheticDocument}); |
|
265 } |
|
266 }, |
|
267 }; |
|
268 DocumentObserver.init(); |
|
269 |
|
270 const ZoomManager = { |
|
271 get fullZoom() { |
|
272 return this._cache.fullZoom; |
|
273 }, |
|
274 |
|
275 get textZoom() { |
|
276 return this._cache.textZoom; |
|
277 }, |
|
278 |
|
279 set fullZoom(value) { |
|
280 this._cache.fullZoom = value; |
|
281 this._markupViewer.fullZoom = value; |
|
282 }, |
|
283 |
|
284 set textZoom(value) { |
|
285 this._cache.textZoom = value; |
|
286 this._markupViewer.textZoom = value; |
|
287 }, |
|
288 |
|
289 refreshFullZoom: function() { |
|
290 return this._refreshZoomValue('fullZoom'); |
|
291 }, |
|
292 |
|
293 refreshTextZoom: function() { |
|
294 return this._refreshZoomValue('textZoom'); |
|
295 }, |
|
296 |
|
297 /** |
|
298 * Retrieves specified zoom property value from markupViewer and refreshes |
|
299 * cache if needed. |
|
300 * @param valueName Either 'fullZoom' or 'textZoom'. |
|
301 * @returns Returns true if cached value was actually refreshed. |
|
302 * @private |
|
303 */ |
|
304 _refreshZoomValue: function(valueName) { |
|
305 let actualZoomValue = this._markupViewer[valueName]; |
|
306 if (actualZoomValue != this._cache[valueName]) { |
|
307 this._cache[valueName] = actualZoomValue; |
|
308 return true; |
|
309 } |
|
310 return false; |
|
311 }, |
|
312 |
|
313 get _markupViewer() { |
|
314 return docShell.contentViewer.QueryInterface(Ci.nsIMarkupDocumentViewer); |
|
315 }, |
|
316 |
|
317 _cache: { |
|
318 fullZoom: NaN, |
|
319 textZoom: NaN |
|
320 } |
|
321 }; |
|
322 |
|
323 addMessageListener("FullZoom", function (aMessage) { |
|
324 ZoomManager.fullZoom = aMessage.data.value; |
|
325 }); |
|
326 |
|
327 addMessageListener("TextZoom", function (aMessage) { |
|
328 ZoomManager.textZoom = aMessage.data.value; |
|
329 }); |
|
330 |
|
331 addEventListener("FullZoomChange", function () { |
|
332 if (ZoomManager.refreshFullZoom()) { |
|
333 sendAsyncMessage("FullZoomChange", { value: ZoomManager.fullZoom}); |
|
334 } |
|
335 }, false); |
|
336 |
|
337 addEventListener("TextZoomChange", function (aEvent) { |
|
338 if (ZoomManager.refreshTextZoom()) { |
|
339 sendAsyncMessage("TextZoomChange", { value: ZoomManager.textZoom}); |
|
340 } |
|
341 }, false); |
|
342 |
|
343 RemoteAddonsChild.init(this); |
|
344 |
|
345 addMessageListener("NetworkPrioritizer:AdjustPriority", (msg) => { |
|
346 let webNav = docShell.QueryInterface(Ci.nsIWebNavigation); |
|
347 let loadGroup = webNav.QueryInterface(Ci.nsIDocumentLoader) |
|
348 .loadGroup.QueryInterface(Ci.nsISupportsPriority); |
|
349 loadGroup.adjustPriority(msg.data.adjustment); |
|
350 }); |
|
351 |
|
352 let AutoCompletePopup = { |
|
353 QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompletePopup]), |
|
354 |
|
355 init: function() { |
|
356 // Hook up the form fill autocomplete controller. |
|
357 let controller = Cc["@mozilla.org/satchel/form-fill-controller;1"] |
|
358 .getService(Ci.nsIFormFillController); |
|
359 |
|
360 controller.attachToBrowser(docShell, this.QueryInterface(Ci.nsIAutoCompletePopup)); |
|
361 |
|
362 this._input = null; |
|
363 this._popupOpen = false; |
|
364 |
|
365 addMessageListener("FormAutoComplete:HandleEnter", message => { |
|
366 this.selectedIndex = message.data.selectedIndex; |
|
367 |
|
368 let controller = Components.classes["@mozilla.org/autocomplete/controller;1"]. |
|
369 getService(Components.interfaces.nsIAutoCompleteController); |
|
370 controller.handleEnter(message.data.isPopupSelection); |
|
371 }); |
|
372 }, |
|
373 |
|
374 get input () { return this._input; }, |
|
375 get overrideValue () { return null; }, |
|
376 set selectedIndex (index) { }, |
|
377 get selectedIndex () { |
|
378 // selectedIndex getter must be synchronous because we need the |
|
379 // correct value when the controller is in controller::HandleEnter. |
|
380 // We can't easily just let the parent inform us the new value every |
|
381 // time it changes because not every action that can change the |
|
382 // selectedIndex is trivial to catch (e.g. moving the mouse over the |
|
383 // list). |
|
384 return sendSyncMessage("FormAutoComplete:GetSelectedIndex", {}); |
|
385 }, |
|
386 get popupOpen () { |
|
387 return this._popupOpen; |
|
388 }, |
|
389 |
|
390 openAutocompletePopup: function (input, element) { |
|
391 this._input = input; |
|
392 this._popupOpen = true; |
|
393 }, |
|
394 |
|
395 closePopup: function () { |
|
396 this._popupOpen = false; |
|
397 sendAsyncMessage("FormAutoComplete:ClosePopup", {}); |
|
398 }, |
|
399 |
|
400 invalidate: function () { |
|
401 }, |
|
402 |
|
403 selectBy: function(reverse, page) { |
|
404 this._index = sendSyncMessage("FormAutoComplete:SelectBy", { |
|
405 reverse: reverse, |
|
406 page: page |
|
407 }); |
|
408 } |
|
409 } |
|
410 |
|
411 let [initData] = sendSyncMessage("Browser:Init"); |
|
412 docShell.useGlobalHistory = initData.useGlobalHistory; |
|
413 if (initData.initPopup) { |
|
414 setTimeout(function() AutoCompletePopup.init(), 0); |
|
415 } |