|
1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 let Cc = Components.classes; |
|
7 let Ci = Components.interfaces; |
|
8 let Cu = Components.utils; |
|
9 |
|
10 Cu.import("resource://gre/modules/Services.jsm"); |
|
11 Cu.import("resource://gre/modules/FormData.jsm"); |
|
12 Cu.import("resource://gre/modules/ScrollPosition.jsm"); |
|
13 Cu.import("resource://gre/modules/Timer.jsm", this); |
|
14 |
|
15 let WebProgressListener = { |
|
16 _lastLocation: null, |
|
17 _firstPaint: false, |
|
18 |
|
19 init: function() { |
|
20 let flags = Ci.nsIWebProgress.NOTIFY_LOCATION | |
|
21 Ci.nsIWebProgress.NOTIFY_SECURITY | |
|
22 Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | |
|
23 Ci.nsIWebProgress.NOTIFY_STATE_NETWORK | |
|
24 Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT; |
|
25 |
|
26 let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress); |
|
27 webProgress.addProgressListener(this, flags); |
|
28 }, |
|
29 |
|
30 onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { |
|
31 if (content != aWebProgress.DOMWindow) |
|
32 return; |
|
33 |
|
34 sendAsyncMessage("Content:StateChange", { |
|
35 contentWindowId: this.contentWindowId, |
|
36 stateFlags: aStateFlags, |
|
37 status: aStatus |
|
38 }); |
|
39 }, |
|
40 |
|
41 onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) { |
|
42 if (content != aWebProgress.DOMWindow) |
|
43 return; |
|
44 |
|
45 let spec = aLocationURI ? aLocationURI.spec : ""; |
|
46 let location = spec.split("#")[0]; |
|
47 |
|
48 let charset = content.document.characterSet; |
|
49 |
|
50 sendAsyncMessage("Content:LocationChange", { |
|
51 contentWindowId: this.contentWindowId, |
|
52 documentURI: aWebProgress.DOMWindow.document.documentURIObject.spec, |
|
53 location: spec, |
|
54 canGoBack: docShell.canGoBack, |
|
55 canGoForward: docShell.canGoForward, |
|
56 charset: charset.toString() |
|
57 }); |
|
58 |
|
59 this._firstPaint = false; |
|
60 let self = this; |
|
61 |
|
62 // Keep track of hash changes |
|
63 this.hashChanged = (location == this._lastLocation); |
|
64 this._lastLocation = location; |
|
65 |
|
66 // When a new page is loaded fire a message for the first paint |
|
67 addEventListener("MozAfterPaint", function(aEvent) { |
|
68 removeEventListener("MozAfterPaint", arguments.callee, true); |
|
69 |
|
70 self._firstPaint = true; |
|
71 let scrollOffset = ContentScroll.getScrollOffset(content); |
|
72 sendAsyncMessage("Browser:FirstPaint", scrollOffset); |
|
73 }, true); |
|
74 }, |
|
75 |
|
76 onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) { |
|
77 }, |
|
78 |
|
79 onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) { |
|
80 if (content != aWebProgress.DOMWindow) |
|
81 return; |
|
82 |
|
83 let serialization = SecurityUI.getSSLStatusAsString(); |
|
84 |
|
85 sendAsyncMessage("Content:SecurityChange", { |
|
86 contentWindowId: this.contentWindowId, |
|
87 SSLStatusAsString: serialization, |
|
88 state: aState |
|
89 }); |
|
90 }, |
|
91 |
|
92 get contentWindowId() { |
|
93 return content.QueryInterface(Ci.nsIInterfaceRequestor) |
|
94 .getInterface(Ci.nsIDOMWindowUtils) |
|
95 .currentInnerWindowID; |
|
96 }, |
|
97 |
|
98 QueryInterface: function QueryInterface(aIID) { |
|
99 if (aIID.equals(Ci.nsIWebProgressListener) || |
|
100 aIID.equals(Ci.nsISupportsWeakReference) || |
|
101 aIID.equals(Ci.nsISupports)) { |
|
102 return this; |
|
103 } |
|
104 |
|
105 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
106 } |
|
107 }; |
|
108 |
|
109 WebProgressListener.init(); |
|
110 |
|
111 |
|
112 let SecurityUI = { |
|
113 getSSLStatusAsString: function() { |
|
114 let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus; |
|
115 |
|
116 if (status) { |
|
117 let serhelper = Cc["@mozilla.org/network/serialization-helper;1"] |
|
118 .getService(Ci.nsISerializationHelper); |
|
119 |
|
120 status.QueryInterface(Ci.nsISerializable); |
|
121 return serhelper.serializeToString(status); |
|
122 } |
|
123 |
|
124 return null; |
|
125 } |
|
126 }; |
|
127 |
|
128 let WebNavigation = { |
|
129 _webNavigation: docShell.QueryInterface(Ci.nsIWebNavigation), |
|
130 _timer: null, |
|
131 |
|
132 init: function() { |
|
133 addMessageListener("WebNavigation:GoBack", this); |
|
134 addMessageListener("WebNavigation:GoForward", this); |
|
135 addMessageListener("WebNavigation:GotoIndex", this); |
|
136 addMessageListener("WebNavigation:LoadURI", this); |
|
137 addMessageListener("WebNavigation:Reload", this); |
|
138 addMessageListener("WebNavigation:Stop", this); |
|
139 }, |
|
140 |
|
141 receiveMessage: function(message) { |
|
142 switch (message.name) { |
|
143 case "WebNavigation:GoBack": |
|
144 this.goBack(); |
|
145 break; |
|
146 case "WebNavigation:GoForward": |
|
147 this.goForward(); |
|
148 break; |
|
149 case "WebNavigation:GotoIndex": |
|
150 this.gotoIndex(message); |
|
151 break; |
|
152 case "WebNavigation:LoadURI": |
|
153 this.loadURI(message); |
|
154 break; |
|
155 case "WebNavigation:Reload": |
|
156 this.reload(message); |
|
157 break; |
|
158 case "WebNavigation:Stop": |
|
159 this.stop(message); |
|
160 break; |
|
161 } |
|
162 }, |
|
163 |
|
164 goBack: function() { |
|
165 if (this._webNavigation.canGoBack) |
|
166 this._webNavigation.goBack(); |
|
167 }, |
|
168 |
|
169 goForward: function() { |
|
170 if (this._webNavigation.canGoForward) |
|
171 this._webNavigation.goForward(); |
|
172 }, |
|
173 |
|
174 gotoIndex: function(message) { |
|
175 this._webNavigation.gotoIndex(message.index); |
|
176 }, |
|
177 |
|
178 loadURI: function(message) { |
|
179 let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE; |
|
180 this._webNavigation.loadURI(message.json.uri, flags, null, null, null); |
|
181 |
|
182 let tabData = message.json; |
|
183 if (tabData.entries) { |
|
184 // We are going to load from history so kill the current load. We do not |
|
185 // want the load added to the history anyway. We reload after resetting history |
|
186 this._webNavigation.stop(this._webNavigation.STOP_ALL); |
|
187 this._restoreHistory(tabData, 0); |
|
188 } |
|
189 }, |
|
190 |
|
191 reload: function(message) { |
|
192 let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE; |
|
193 this._webNavigation.reload(flags); |
|
194 }, |
|
195 |
|
196 stop: function(message) { |
|
197 let flags = message.json.flags || this._webNavigation.STOP_ALL; |
|
198 this._webNavigation.stop(flags); |
|
199 }, |
|
200 |
|
201 _restoreHistory: function _restoreHistory(aTabData, aCount) { |
|
202 // We need to wait for the sessionHistory to be initialized and there |
|
203 // is no good way to do this. We'll try a wait loop like desktop |
|
204 try { |
|
205 if (!this._webNavigation.sessionHistory) |
|
206 throw new Error(); |
|
207 } catch (ex) { |
|
208 if (aCount < 10) { |
|
209 let self = this; |
|
210 this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
|
211 this._timer.initWithCallback(function(aTimer) { |
|
212 self._timer = null; |
|
213 self._restoreHistory(aTabData, aCount + 1); |
|
214 }, 100, Ci.nsITimer.TYPE_ONE_SHOT); |
|
215 return; |
|
216 } |
|
217 } |
|
218 |
|
219 let history = this._webNavigation.sessionHistory; |
|
220 if (history.count > 0) |
|
221 history.PurgeHistory(history.count); |
|
222 history.QueryInterface(Ci.nsISHistoryInternal); |
|
223 |
|
224 // helper hashes for ensuring unique frame IDs and unique document |
|
225 // identifiers. |
|
226 let idMap = { used: {} }; |
|
227 let docIdentMap = {}; |
|
228 |
|
229 for (let i = 0; i < aTabData.entries.length; i++) { |
|
230 if (!aTabData.entries[i].url) |
|
231 continue; |
|
232 history.addEntry(this._deserializeHistoryEntry(aTabData.entries[i], idMap, docIdentMap), true); |
|
233 } |
|
234 |
|
235 // We need to force set the active history item and cause it to reload since |
|
236 // we stop the load above |
|
237 let activeIndex = (aTabData.index || aTabData.entries.length) - 1; |
|
238 history.getEntryAtIndex(activeIndex, true); |
|
239 history.QueryInterface(Ci.nsISHistory).reloadCurrentEntry(); |
|
240 }, |
|
241 |
|
242 _deserializeHistoryEntry: function _deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) { |
|
243 let shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].createInstance(Ci.nsISHEntry); |
|
244 |
|
245 shEntry.setURI(Services.io.newURI(aEntry.url, null, null)); |
|
246 shEntry.setTitle(aEntry.title || aEntry.url); |
|
247 if (aEntry.subframe) |
|
248 shEntry.setIsSubFrame(aEntry.subframe || false); |
|
249 shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory; |
|
250 if (aEntry.contentType) |
|
251 shEntry.contentType = aEntry.contentType; |
|
252 if (aEntry.referrer) |
|
253 shEntry.referrerURI = Services.io.newURI(aEntry.referrer, null, null); |
|
254 |
|
255 if (aEntry.cacheKey) { |
|
256 let cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].createInstance(Ci.nsISupportsPRUint32); |
|
257 cacheKey.data = aEntry.cacheKey; |
|
258 shEntry.cacheKey = cacheKey; |
|
259 } |
|
260 |
|
261 if (aEntry.ID) { |
|
262 // get a new unique ID for this frame (since the one from the last |
|
263 // start might already be in use) |
|
264 let id = aIdMap[aEntry.ID] || 0; |
|
265 if (!id) { |
|
266 for (id = Date.now(); id in aIdMap.used; id++); |
|
267 aIdMap[aEntry.ID] = id; |
|
268 aIdMap.used[id] = true; |
|
269 } |
|
270 shEntry.ID = id; |
|
271 } |
|
272 |
|
273 if (aEntry.docshellID) |
|
274 shEntry.docshellID = aEntry.docshellID; |
|
275 |
|
276 if (aEntry.structuredCloneState && aEntry.structuredCloneVersion) { |
|
277 shEntry.stateData = |
|
278 Cc["@mozilla.org/docshell/structured-clone-container;1"]. |
|
279 createInstance(Ci.nsIStructuredCloneContainer); |
|
280 |
|
281 shEntry.stateData.initFromBase64(aEntry.structuredCloneState, aEntry.structuredCloneVersion); |
|
282 } |
|
283 |
|
284 if (aEntry.scroll) { |
|
285 let scrollPos = aEntry.scroll.split(","); |
|
286 scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0]; |
|
287 shEntry.setScrollPosition(scrollPos[0], scrollPos[1]); |
|
288 } |
|
289 |
|
290 let childDocIdents = {}; |
|
291 if (aEntry.docIdentifier) { |
|
292 // If we have a serialized document identifier, try to find an SHEntry |
|
293 // which matches that doc identifier and adopt that SHEntry's |
|
294 // BFCacheEntry. If we don't find a match, insert shEntry as the match |
|
295 // for the document identifier. |
|
296 let matchingEntry = aDocIdentMap[aEntry.docIdentifier]; |
|
297 if (!matchingEntry) { |
|
298 matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents}; |
|
299 aDocIdentMap[aEntry.docIdentifier] = matchingEntry; |
|
300 } else { |
|
301 shEntry.adoptBFCacheEntry(matchingEntry.shEntry); |
|
302 childDocIdents = matchingEntry.childDocIdents; |
|
303 } |
|
304 } |
|
305 |
|
306 if (aEntry.owner_b64) { |
|
307 let ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); |
|
308 let binaryData = atob(aEntry.owner_b64); |
|
309 ownerInput.setData(binaryData, binaryData.length); |
|
310 let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIObjectInputStream); |
|
311 binaryStream.setInputStream(ownerInput); |
|
312 try { // Catch possible deserialization exceptions |
|
313 shEntry.owner = binaryStream.readObject(true); |
|
314 } catch (ex) { dump(ex); } |
|
315 } |
|
316 |
|
317 if (aEntry.children && shEntry instanceof Ci.nsISHContainer) { |
|
318 for (let i = 0; i < aEntry.children.length; i++) { |
|
319 if (!aEntry.children[i].url) |
|
320 continue; |
|
321 |
|
322 // We're getting sessionrestore.js files with a cycle in the |
|
323 // doc-identifier graph, likely due to bug 698656. (That is, we have |
|
324 // an entry where doc identifier A is an ancestor of doc identifier B, |
|
325 // and another entry where doc identifier B is an ancestor of A.) |
|
326 // |
|
327 // If we were to respect these doc identifiers, we'd create a cycle in |
|
328 // the SHEntries themselves, which causes the docshell to loop forever |
|
329 // when it looks for the root SHEntry. |
|
330 // |
|
331 // So as a hack to fix this, we restrict the scope of a doc identifier |
|
332 // to be a node's siblings and cousins, and pass childDocIdents, not |
|
333 // aDocIdents, to _deserializeHistoryEntry. That is, we say that two |
|
334 // SHEntries with the same doc identifier have the same document iff |
|
335 // they have the same parent or their parents have the same document. |
|
336 |
|
337 shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap, childDocIdents), i); |
|
338 } |
|
339 } |
|
340 |
|
341 return shEntry; |
|
342 }, |
|
343 |
|
344 sendHistory: function sendHistory() { |
|
345 // We need to package up the session history and send it to the sessionstore |
|
346 let entries = []; |
|
347 let history = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory; |
|
348 for (let i = 0; i < history.count; i++) { |
|
349 let entry = this._serializeHistoryEntry(history.getEntryAtIndex(i, false)); |
|
350 |
|
351 // If someone directly navigates to one of these URLs and they switch to Desktop, |
|
352 // we need to make the page load-able. |
|
353 if (entry.url == "about:home" || entry.url == "about:start") { |
|
354 entry.url = "about:newtab"; |
|
355 } |
|
356 entries.push(entry); |
|
357 } |
|
358 let index = history.index + 1; |
|
359 sendAsyncMessage("Content:SessionHistory", { entries: entries, index: index }); |
|
360 }, |
|
361 |
|
362 _serializeHistoryEntry: function _serializeHistoryEntry(aEntry) { |
|
363 let entry = { url: aEntry.URI.spec }; |
|
364 |
|
365 if (Util.isURLEmpty(entry.url)) { |
|
366 entry.title = Util.getEmptyURLTabTitle(); |
|
367 } else { |
|
368 entry.title = aEntry.title; |
|
369 } |
|
370 |
|
371 if (!(aEntry instanceof Ci.nsISHEntry)) |
|
372 return entry; |
|
373 |
|
374 let cacheKey = aEntry.cacheKey; |
|
375 if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 && cacheKey.data != 0) |
|
376 entry.cacheKey = cacheKey.data; |
|
377 |
|
378 entry.ID = aEntry.ID; |
|
379 entry.docshellID = aEntry.docshellID; |
|
380 |
|
381 if (aEntry.referrerURI) |
|
382 entry.referrer = aEntry.referrerURI.spec; |
|
383 |
|
384 if (aEntry.contentType) |
|
385 entry.contentType = aEntry.contentType; |
|
386 |
|
387 let x = {}, y = {}; |
|
388 aEntry.getScrollPosition(x, y); |
|
389 if (x.value != 0 || y.value != 0) |
|
390 entry.scroll = x.value + "," + y.value; |
|
391 |
|
392 if (aEntry.owner) { |
|
393 try { |
|
394 let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIObjectOutputStream); |
|
395 let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); |
|
396 pipe.init(false, false, 0, 0xffffffff, null); |
|
397 binaryStream.setOutputStream(pipe.outputStream); |
|
398 binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true); |
|
399 binaryStream.close(); |
|
400 |
|
401 // Now we want to read the data from the pipe's input end and encode it. |
|
402 let scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream); |
|
403 scriptableStream.setInputStream(pipe.inputStream); |
|
404 let ownerBytes = scriptableStream.readByteArray(scriptableStream.available()); |
|
405 // We can stop doing base64 encoding once our serialization into JSON |
|
406 // is guaranteed to handle all chars in strings, including embedded |
|
407 // nulls. |
|
408 entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes)); |
|
409 } catch (e) { dump(e); } |
|
410 } |
|
411 |
|
412 entry.docIdentifier = aEntry.BFCacheEntry.ID; |
|
413 |
|
414 if (aEntry.stateData != null) { |
|
415 entry.structuredCloneState = aEntry.stateData.getDataAsBase64(); |
|
416 entry.structuredCloneVersion = aEntry.stateData.formatVersion; |
|
417 } |
|
418 |
|
419 if (!(aEntry instanceof Ci.nsISHContainer)) |
|
420 return entry; |
|
421 |
|
422 if (aEntry.childCount > 0) { |
|
423 entry.children = []; |
|
424 for (let i = 0; i < aEntry.childCount; i++) { |
|
425 let child = aEntry.GetChildAt(i); |
|
426 if (child) |
|
427 entry.children.push(this._serializeHistoryEntry(child)); |
|
428 else // to maintain the correct frame order, insert a dummy entry |
|
429 entry.children.push({ url: "about:blank" }); |
|
430 |
|
431 // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595) |
|
432 if (/^wyciwyg:\/\//.test(entry.children[i].url)) { |
|
433 delete entry.children; |
|
434 break; |
|
435 } |
|
436 } |
|
437 } |
|
438 |
|
439 return entry; |
|
440 } |
|
441 }; |
|
442 |
|
443 WebNavigation.init(); |
|
444 |
|
445 |
|
446 let DOMEvents = { |
|
447 _timeout: null, |
|
448 _sessionEvents: new Set(), |
|
449 _sessionEventMap: {"SessionStore:collectFormdata" : FormData.collect, |
|
450 "SessionStore:collectScrollPosition" : ScrollPosition.collect}, |
|
451 |
|
452 init: function() { |
|
453 addEventListener("DOMContentLoaded", this, false); |
|
454 addEventListener("DOMTitleChanged", this, false); |
|
455 addEventListener("DOMLinkAdded", this, false); |
|
456 addEventListener("DOMWillOpenModalDialog", this, false); |
|
457 addEventListener("DOMModalDialogClosed", this, true); |
|
458 addEventListener("DOMWindowClose", this, false); |
|
459 addEventListener("DOMPopupBlocked", this, false); |
|
460 addEventListener("pageshow", this, false); |
|
461 addEventListener("pagehide", this, false); |
|
462 |
|
463 addEventListener("input", this, true); |
|
464 addEventListener("change", this, true); |
|
465 addEventListener("scroll", this, true); |
|
466 addMessageListener("SessionStore:restoreSessionTabData", this); |
|
467 }, |
|
468 |
|
469 receiveMessage: function(message) { |
|
470 switch (message.name) { |
|
471 case "SessionStore:restoreSessionTabData": |
|
472 if (message.json.formdata) |
|
473 FormData.restore(content, message.json.formdata); |
|
474 if (message.json.scroll) |
|
475 ScrollPosition.restore(content, message.json.scroll.scroll); |
|
476 break; |
|
477 } |
|
478 }, |
|
479 |
|
480 handleEvent: function(aEvent) { |
|
481 let document = content.document; |
|
482 switch (aEvent.type) { |
|
483 case "DOMContentLoaded": |
|
484 if (document.documentURIObject.spec == "about:blank") |
|
485 return; |
|
486 |
|
487 sendAsyncMessage("DOMContentLoaded", { }); |
|
488 |
|
489 // Send the session history now too |
|
490 WebNavigation.sendHistory(); |
|
491 break; |
|
492 |
|
493 case "pageshow": |
|
494 case "pagehide": { |
|
495 if (aEvent.target.defaultView != content) |
|
496 break; |
|
497 |
|
498 let util = aEvent.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) |
|
499 .getInterface(Ci.nsIDOMWindowUtils); |
|
500 |
|
501 let json = { |
|
502 contentWindowWidth: content.innerWidth, |
|
503 contentWindowHeight: content.innerHeight, |
|
504 windowId: util.outerWindowID, |
|
505 persisted: aEvent.persisted |
|
506 }; |
|
507 |
|
508 // Clear onload focus to prevent the VKB to be shown unexpectingly |
|
509 // but only if the location has really changed and not only the |
|
510 // fragment identifier |
|
511 let contentWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; |
|
512 if (!WebProgressListener.hashChanged && contentWindowID == util.currentInnerWindowID) { |
|
513 let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); |
|
514 focusManager.clearFocus(content); |
|
515 } |
|
516 |
|
517 sendAsyncMessage(aEvent.type, json); |
|
518 break; |
|
519 } |
|
520 |
|
521 case "DOMPopupBlocked": { |
|
522 let util = aEvent.requestingWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
|
523 .getInterface(Ci.nsIDOMWindowUtils); |
|
524 let json = { |
|
525 windowId: util.outerWindowID, |
|
526 popupWindowURI: { |
|
527 spec: aEvent.popupWindowURI.spec, |
|
528 charset: aEvent.popupWindowURI.originCharset |
|
529 }, |
|
530 popupWindowFeatures: aEvent.popupWindowFeatures, |
|
531 popupWindowName: aEvent.popupWindowName |
|
532 }; |
|
533 |
|
534 sendAsyncMessage("DOMPopupBlocked", json); |
|
535 break; |
|
536 } |
|
537 |
|
538 case "DOMTitleChanged": |
|
539 sendAsyncMessage("DOMTitleChanged", { title: document.title }); |
|
540 break; |
|
541 |
|
542 case "DOMLinkAdded": |
|
543 let target = aEvent.originalTarget; |
|
544 if (!target.href || target.disabled) |
|
545 return; |
|
546 |
|
547 let json = { |
|
548 windowId: target.ownerDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID, |
|
549 href: target.href, |
|
550 charset: document.characterSet, |
|
551 title: target.title, |
|
552 rel: target.rel, |
|
553 type: target.type |
|
554 }; |
|
555 |
|
556 // rel=icon can also have a sizes attribute |
|
557 if (target.hasAttribute("sizes")) |
|
558 json.sizes = target.getAttribute("sizes"); |
|
559 |
|
560 sendAsyncMessage("DOMLinkAdded", json); |
|
561 break; |
|
562 |
|
563 case "DOMWillOpenModalDialog": |
|
564 case "DOMModalDialogClosed": |
|
565 case "DOMWindowClose": |
|
566 let retvals = sendSyncMessage(aEvent.type, { }); |
|
567 for (let i in retvals) { |
|
568 if (retvals[i].preventDefault) { |
|
569 aEvent.preventDefault(); |
|
570 break; |
|
571 } |
|
572 } |
|
573 break; |
|
574 case "input": |
|
575 case "change": |
|
576 this._sessionEvents.add("SessionStore:collectFormdata"); |
|
577 this._sendUpdates(); |
|
578 break; |
|
579 case "scroll": |
|
580 this._sessionEvents.add("SessionStore:collectScrollPosition"); |
|
581 this._sendUpdates(); |
|
582 break; |
|
583 } |
|
584 }, |
|
585 |
|
586 _sendUpdates: function() { |
|
587 if (!this._timeout) { |
|
588 // Wait a little before sending the message to batch multiple changes. |
|
589 this._timeout = setTimeout(function() { |
|
590 for (let eventType of this._sessionEvents) { |
|
591 sendAsyncMessage(eventType, { |
|
592 data: this._sessionEventMap[eventType](content) |
|
593 }); |
|
594 } |
|
595 this._sessionEvents.clear(); |
|
596 clearTimeout(this._timeout); |
|
597 this._timeout = null; |
|
598 }.bind(this), 1000); |
|
599 } |
|
600 } |
|
601 }; |
|
602 |
|
603 DOMEvents.init(); |
|
604 |
|
605 let ContentScroll = { |
|
606 // The most recent offset set by APZC for the root scroll frame |
|
607 _scrollOffset: { x: 0, y: 0 }, |
|
608 |
|
609 init: function() { |
|
610 addMessageListener("Content:SetWindowSize", this); |
|
611 |
|
612 addEventListener("pagehide", this, false); |
|
613 addEventListener("MozScrolledAreaChanged", this, false); |
|
614 }, |
|
615 |
|
616 getScrollOffset: function(aWindow) { |
|
617 let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); |
|
618 let scrollX = {}, scrollY = {}; |
|
619 cwu.getScrollXY(false, scrollX, scrollY); |
|
620 return { x: scrollX.value, y: scrollY.value }; |
|
621 }, |
|
622 |
|
623 getScrollOffsetForElement: function(aElement) { |
|
624 if (aElement.parentNode == aElement.ownerDocument) |
|
625 return this.getScrollOffset(aElement.ownerDocument.defaultView); |
|
626 return { x: aElement.scrollLeft, y: aElement.scrollTop }; |
|
627 }, |
|
628 |
|
629 receiveMessage: function(aMessage) { |
|
630 let json = aMessage.json; |
|
631 switch (aMessage.name) { |
|
632 case "Content:SetWindowSize": { |
|
633 let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); |
|
634 cwu.setCSSViewport(json.width, json.height); |
|
635 sendAsyncMessage("Content:SetWindowSize:Complete", {}); |
|
636 break; |
|
637 } |
|
638 } |
|
639 }, |
|
640 |
|
641 handleEvent: function(aEvent) { |
|
642 switch (aEvent.type) { |
|
643 case "pagehide": |
|
644 this._scrollOffset = { x: 0, y: 0 }; |
|
645 break; |
|
646 |
|
647 case "MozScrolledAreaChanged": { |
|
648 let doc = aEvent.originalTarget; |
|
649 if (content != doc.defaultView) // We are only interested in root scroll pane changes |
|
650 return; |
|
651 |
|
652 sendAsyncMessage("MozScrolledAreaChanged", { |
|
653 width: aEvent.width, |
|
654 height: aEvent.height, |
|
655 left: aEvent.x + content.scrollX |
|
656 }); |
|
657 |
|
658 // Send event only after painting to make sure content views in the parent process have |
|
659 // been updated. |
|
660 addEventListener("MozAfterPaint", function afterPaint() { |
|
661 removeEventListener("MozAfterPaint", afterPaint, false); |
|
662 sendAsyncMessage("Content:UpdateDisplayPort"); |
|
663 }, false); |
|
664 |
|
665 break; |
|
666 } |
|
667 } |
|
668 } |
|
669 }; |
|
670 this.ContentScroll = ContentScroll; |
|
671 |
|
672 ContentScroll.init(); |
|
673 |
|
674 let ContentActive = { |
|
675 init: function() { |
|
676 addMessageListener("Content:Activate", this); |
|
677 addMessageListener("Content:Deactivate", this); |
|
678 }, |
|
679 |
|
680 receiveMessage: function(aMessage) { |
|
681 let json = aMessage.json; |
|
682 switch (aMessage.name) { |
|
683 case "Content:Deactivate": |
|
684 docShell.isActive = false; |
|
685 let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); |
|
686 if (json.keepviewport) |
|
687 break; |
|
688 cwu.setDisplayPortForElement(0, 0, 0, 0, content.document.documentElement, 0); |
|
689 break; |
|
690 |
|
691 case "Content:Activate": |
|
692 docShell.isActive = true; |
|
693 break; |
|
694 } |
|
695 } |
|
696 }; |
|
697 |
|
698 ContentActive.init(); |
|
699 |
|
700 /** |
|
701 * Helper class for IndexedDB, child part. Listens using |
|
702 * the observer service for events regarding IndexedDB |
|
703 * prompts, and sends messages to the parent to actually |
|
704 * show the prompts. |
|
705 */ |
|
706 let IndexedDB = { |
|
707 _permissionsPrompt: "indexedDB-permissions-prompt", |
|
708 _permissionsResponse: "indexedDB-permissions-response", |
|
709 |
|
710 _quotaPrompt: "indexedDB-quota-prompt", |
|
711 _quotaResponse: "indexedDB-quota-response", |
|
712 _quotaCancel: "indexedDB-quota-cancel", |
|
713 |
|
714 waitingObservers: [], |
|
715 |
|
716 init: function IndexedDBPromptHelper_init() { |
|
717 let os = Services.obs; |
|
718 os.addObserver(this, this._permissionsPrompt, false); |
|
719 os.addObserver(this, this._quotaPrompt, false); |
|
720 os.addObserver(this, this._quotaCancel, false); |
|
721 addMessageListener("IndexedDB:Response", this); |
|
722 }, |
|
723 |
|
724 observe: function IndexedDBPromptHelper_observe(aSubject, aTopic, aData) { |
|
725 if (aTopic != this._permissionsPrompt && aTopic != this._quotaPrompt && aTopic != this._quotaCancel) { |
|
726 throw new Error("Unexpected topic!"); |
|
727 } |
|
728 |
|
729 let requestor = aSubject.QueryInterface(Ci.nsIInterfaceRequestor); |
|
730 let observer = requestor.getInterface(Ci.nsIObserver); |
|
731 |
|
732 let contentWindow = requestor.getInterface(Ci.nsIDOMWindow); |
|
733 let contentDocument = contentWindow.document; |
|
734 |
|
735 if (aTopic == this._quotaCancel) { |
|
736 observer.observe(null, this._quotaResponse, Ci.nsIPermissionManager.UNKNOWN_ACTION); |
|
737 return; |
|
738 } |
|
739 |
|
740 // Remote to parent |
|
741 sendAsyncMessage("IndexedDB:Prompt", { |
|
742 topic: aTopic, |
|
743 host: contentDocument.documentURIObject.asciiHost, |
|
744 location: contentDocument.location.toString(), |
|
745 data: aData, |
|
746 observerId: this.addWaitingObserver(observer) |
|
747 }); |
|
748 }, |
|
749 |
|
750 receiveMessage: function(aMessage) { |
|
751 let payload = aMessage.json; |
|
752 switch (aMessage.name) { |
|
753 case "IndexedDB:Response": |
|
754 let observer = this.getAndRemoveWaitingObserver(payload.observerId); |
|
755 observer.observe(null, payload.responseTopic, payload.permission); |
|
756 } |
|
757 }, |
|
758 |
|
759 addWaitingObserver: function(aObserver) { |
|
760 let observerId = 0; |
|
761 while (observerId in this.waitingObservers) |
|
762 observerId++; |
|
763 this.waitingObservers[observerId] = aObserver; |
|
764 return observerId; |
|
765 }, |
|
766 |
|
767 getAndRemoveWaitingObserver: function(aObserverId) { |
|
768 let observer = this.waitingObservers[aObserverId]; |
|
769 delete this.waitingObservers[aObserverId]; |
|
770 return observer; |
|
771 } |
|
772 }; |
|
773 |
|
774 IndexedDB.init(); |
|
775 |