michael@0: // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: let Cc = Components.classes; michael@0: let Ci = Components.interfaces; michael@0: let Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/FormData.jsm"); michael@0: Cu.import("resource://gre/modules/ScrollPosition.jsm"); michael@0: Cu.import("resource://gre/modules/Timer.jsm", this); michael@0: michael@0: let WebProgressListener = { michael@0: _lastLocation: null, michael@0: _firstPaint: false, michael@0: michael@0: init: function() { michael@0: let flags = Ci.nsIWebProgress.NOTIFY_LOCATION | michael@0: Ci.nsIWebProgress.NOTIFY_SECURITY | michael@0: Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | michael@0: Ci.nsIWebProgress.NOTIFY_STATE_NETWORK | michael@0: Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT; michael@0: michael@0: let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress); michael@0: webProgress.addProgressListener(this, flags); michael@0: }, michael@0: michael@0: onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { michael@0: if (content != aWebProgress.DOMWindow) michael@0: return; michael@0: michael@0: sendAsyncMessage("Content:StateChange", { michael@0: contentWindowId: this.contentWindowId, michael@0: stateFlags: aStateFlags, michael@0: status: aStatus michael@0: }); michael@0: }, michael@0: michael@0: onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) { michael@0: if (content != aWebProgress.DOMWindow) michael@0: return; michael@0: michael@0: let spec = aLocationURI ? aLocationURI.spec : ""; michael@0: let location = spec.split("#")[0]; michael@0: michael@0: let charset = content.document.characterSet; michael@0: michael@0: sendAsyncMessage("Content:LocationChange", { michael@0: contentWindowId: this.contentWindowId, michael@0: documentURI: aWebProgress.DOMWindow.document.documentURIObject.spec, michael@0: location: spec, michael@0: canGoBack: docShell.canGoBack, michael@0: canGoForward: docShell.canGoForward, michael@0: charset: charset.toString() michael@0: }); michael@0: michael@0: this._firstPaint = false; michael@0: let self = this; michael@0: michael@0: // Keep track of hash changes michael@0: this.hashChanged = (location == this._lastLocation); michael@0: this._lastLocation = location; michael@0: michael@0: // When a new page is loaded fire a message for the first paint michael@0: addEventListener("MozAfterPaint", function(aEvent) { michael@0: removeEventListener("MozAfterPaint", arguments.callee, true); michael@0: michael@0: self._firstPaint = true; michael@0: let scrollOffset = ContentScroll.getScrollOffset(content); michael@0: sendAsyncMessage("Browser:FirstPaint", scrollOffset); michael@0: }, true); michael@0: }, michael@0: michael@0: onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) { michael@0: }, michael@0: michael@0: onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) { michael@0: if (content != aWebProgress.DOMWindow) michael@0: return; michael@0: michael@0: let serialization = SecurityUI.getSSLStatusAsString(); michael@0: michael@0: sendAsyncMessage("Content:SecurityChange", { michael@0: contentWindowId: this.contentWindowId, michael@0: SSLStatusAsString: serialization, michael@0: state: aState michael@0: }); michael@0: }, michael@0: michael@0: get contentWindowId() { michael@0: return content.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils) michael@0: .currentInnerWindowID; michael@0: }, michael@0: michael@0: QueryInterface: function QueryInterface(aIID) { michael@0: if (aIID.equals(Ci.nsIWebProgressListener) || michael@0: aIID.equals(Ci.nsISupportsWeakReference) || michael@0: aIID.equals(Ci.nsISupports)) { michael@0: return this; michael@0: } michael@0: michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: } michael@0: }; michael@0: michael@0: WebProgressListener.init(); michael@0: michael@0: michael@0: let SecurityUI = { michael@0: getSSLStatusAsString: function() { michael@0: let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus; michael@0: michael@0: if (status) { michael@0: let serhelper = Cc["@mozilla.org/network/serialization-helper;1"] michael@0: .getService(Ci.nsISerializationHelper); michael@0: michael@0: status.QueryInterface(Ci.nsISerializable); michael@0: return serhelper.serializeToString(status); michael@0: } michael@0: michael@0: return null; michael@0: } michael@0: }; michael@0: michael@0: let WebNavigation = { michael@0: _webNavigation: docShell.QueryInterface(Ci.nsIWebNavigation), michael@0: _timer: null, michael@0: michael@0: init: function() { michael@0: addMessageListener("WebNavigation:GoBack", this); michael@0: addMessageListener("WebNavigation:GoForward", this); michael@0: addMessageListener("WebNavigation:GotoIndex", this); michael@0: addMessageListener("WebNavigation:LoadURI", this); michael@0: addMessageListener("WebNavigation:Reload", this); michael@0: addMessageListener("WebNavigation:Stop", this); michael@0: }, michael@0: michael@0: receiveMessage: function(message) { michael@0: switch (message.name) { michael@0: case "WebNavigation:GoBack": michael@0: this.goBack(); michael@0: break; michael@0: case "WebNavigation:GoForward": michael@0: this.goForward(); michael@0: break; michael@0: case "WebNavigation:GotoIndex": michael@0: this.gotoIndex(message); michael@0: break; michael@0: case "WebNavigation:LoadURI": michael@0: this.loadURI(message); michael@0: break; michael@0: case "WebNavigation:Reload": michael@0: this.reload(message); michael@0: break; michael@0: case "WebNavigation:Stop": michael@0: this.stop(message); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: goBack: function() { michael@0: if (this._webNavigation.canGoBack) michael@0: this._webNavigation.goBack(); michael@0: }, michael@0: michael@0: goForward: function() { michael@0: if (this._webNavigation.canGoForward) michael@0: this._webNavigation.goForward(); michael@0: }, michael@0: michael@0: gotoIndex: function(message) { michael@0: this._webNavigation.gotoIndex(message.index); michael@0: }, michael@0: michael@0: loadURI: function(message) { michael@0: let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE; michael@0: this._webNavigation.loadURI(message.json.uri, flags, null, null, null); michael@0: michael@0: let tabData = message.json; michael@0: if (tabData.entries) { michael@0: // We are going to load from history so kill the current load. We do not michael@0: // want the load added to the history anyway. We reload after resetting history michael@0: this._webNavigation.stop(this._webNavigation.STOP_ALL); michael@0: this._restoreHistory(tabData, 0); michael@0: } michael@0: }, michael@0: michael@0: reload: function(message) { michael@0: let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE; michael@0: this._webNavigation.reload(flags); michael@0: }, michael@0: michael@0: stop: function(message) { michael@0: let flags = message.json.flags || this._webNavigation.STOP_ALL; michael@0: this._webNavigation.stop(flags); michael@0: }, michael@0: michael@0: _restoreHistory: function _restoreHistory(aTabData, aCount) { michael@0: // We need to wait for the sessionHistory to be initialized and there michael@0: // is no good way to do this. We'll try a wait loop like desktop michael@0: try { michael@0: if (!this._webNavigation.sessionHistory) michael@0: throw new Error(); michael@0: } catch (ex) { michael@0: if (aCount < 10) { michael@0: let self = this; michael@0: this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: this._timer.initWithCallback(function(aTimer) { michael@0: self._timer = null; michael@0: self._restoreHistory(aTabData, aCount + 1); michael@0: }, 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: let history = this._webNavigation.sessionHistory; michael@0: if (history.count > 0) michael@0: history.PurgeHistory(history.count); michael@0: history.QueryInterface(Ci.nsISHistoryInternal); michael@0: michael@0: // helper hashes for ensuring unique frame IDs and unique document michael@0: // identifiers. michael@0: let idMap = { used: {} }; michael@0: let docIdentMap = {}; michael@0: michael@0: for (let i = 0; i < aTabData.entries.length; i++) { michael@0: if (!aTabData.entries[i].url) michael@0: continue; michael@0: history.addEntry(this._deserializeHistoryEntry(aTabData.entries[i], idMap, docIdentMap), true); michael@0: } michael@0: michael@0: // We need to force set the active history item and cause it to reload since michael@0: // we stop the load above michael@0: let activeIndex = (aTabData.index || aTabData.entries.length) - 1; michael@0: history.getEntryAtIndex(activeIndex, true); michael@0: history.QueryInterface(Ci.nsISHistory).reloadCurrentEntry(); michael@0: }, michael@0: michael@0: _deserializeHistoryEntry: function _deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) { michael@0: let shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].createInstance(Ci.nsISHEntry); michael@0: michael@0: shEntry.setURI(Services.io.newURI(aEntry.url, null, null)); michael@0: shEntry.setTitle(aEntry.title || aEntry.url); michael@0: if (aEntry.subframe) michael@0: shEntry.setIsSubFrame(aEntry.subframe || false); michael@0: shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory; michael@0: if (aEntry.contentType) michael@0: shEntry.contentType = aEntry.contentType; michael@0: if (aEntry.referrer) michael@0: shEntry.referrerURI = Services.io.newURI(aEntry.referrer, null, null); michael@0: michael@0: if (aEntry.cacheKey) { michael@0: let cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].createInstance(Ci.nsISupportsPRUint32); michael@0: cacheKey.data = aEntry.cacheKey; michael@0: shEntry.cacheKey = cacheKey; michael@0: } michael@0: michael@0: if (aEntry.ID) { michael@0: // get a new unique ID for this frame (since the one from the last michael@0: // start might already be in use) michael@0: let id = aIdMap[aEntry.ID] || 0; michael@0: if (!id) { michael@0: for (id = Date.now(); id in aIdMap.used; id++); michael@0: aIdMap[aEntry.ID] = id; michael@0: aIdMap.used[id] = true; michael@0: } michael@0: shEntry.ID = id; michael@0: } michael@0: michael@0: if (aEntry.docshellID) michael@0: shEntry.docshellID = aEntry.docshellID; michael@0: michael@0: if (aEntry.structuredCloneState && aEntry.structuredCloneVersion) { michael@0: shEntry.stateData = michael@0: Cc["@mozilla.org/docshell/structured-clone-container;1"]. michael@0: createInstance(Ci.nsIStructuredCloneContainer); michael@0: michael@0: shEntry.stateData.initFromBase64(aEntry.structuredCloneState, aEntry.structuredCloneVersion); michael@0: } michael@0: michael@0: if (aEntry.scroll) { michael@0: let scrollPos = aEntry.scroll.split(","); michael@0: scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0]; michael@0: shEntry.setScrollPosition(scrollPos[0], scrollPos[1]); michael@0: } michael@0: michael@0: let childDocIdents = {}; michael@0: if (aEntry.docIdentifier) { michael@0: // If we have a serialized document identifier, try to find an SHEntry michael@0: // which matches that doc identifier and adopt that SHEntry's michael@0: // BFCacheEntry. If we don't find a match, insert shEntry as the match michael@0: // for the document identifier. michael@0: let matchingEntry = aDocIdentMap[aEntry.docIdentifier]; michael@0: if (!matchingEntry) { michael@0: matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents}; michael@0: aDocIdentMap[aEntry.docIdentifier] = matchingEntry; michael@0: } else { michael@0: shEntry.adoptBFCacheEntry(matchingEntry.shEntry); michael@0: childDocIdents = matchingEntry.childDocIdents; michael@0: } michael@0: } michael@0: michael@0: if (aEntry.owner_b64) { michael@0: let ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); michael@0: let binaryData = atob(aEntry.owner_b64); michael@0: ownerInput.setData(binaryData, binaryData.length); michael@0: let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIObjectInputStream); michael@0: binaryStream.setInputStream(ownerInput); michael@0: try { // Catch possible deserialization exceptions michael@0: shEntry.owner = binaryStream.readObject(true); michael@0: } catch (ex) { dump(ex); } michael@0: } michael@0: michael@0: if (aEntry.children && shEntry instanceof Ci.nsISHContainer) { michael@0: for (let i = 0; i < aEntry.children.length; i++) { michael@0: if (!aEntry.children[i].url) michael@0: continue; michael@0: michael@0: // We're getting sessionrestore.js files with a cycle in the michael@0: // doc-identifier graph, likely due to bug 698656. (That is, we have michael@0: // an entry where doc identifier A is an ancestor of doc identifier B, michael@0: // and another entry where doc identifier B is an ancestor of A.) michael@0: // michael@0: // If we were to respect these doc identifiers, we'd create a cycle in michael@0: // the SHEntries themselves, which causes the docshell to loop forever michael@0: // when it looks for the root SHEntry. michael@0: // michael@0: // So as a hack to fix this, we restrict the scope of a doc identifier michael@0: // to be a node's siblings and cousins, and pass childDocIdents, not michael@0: // aDocIdents, to _deserializeHistoryEntry. That is, we say that two michael@0: // SHEntries with the same doc identifier have the same document iff michael@0: // they have the same parent or their parents have the same document. michael@0: michael@0: shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap, childDocIdents), i); michael@0: } michael@0: } michael@0: michael@0: return shEntry; michael@0: }, michael@0: michael@0: sendHistory: function sendHistory() { michael@0: // We need to package up the session history and send it to the sessionstore michael@0: let entries = []; michael@0: let history = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory; michael@0: for (let i = 0; i < history.count; i++) { michael@0: let entry = this._serializeHistoryEntry(history.getEntryAtIndex(i, false)); michael@0: michael@0: // If someone directly navigates to one of these URLs and they switch to Desktop, michael@0: // we need to make the page load-able. michael@0: if (entry.url == "about:home" || entry.url == "about:start") { michael@0: entry.url = "about:newtab"; michael@0: } michael@0: entries.push(entry); michael@0: } michael@0: let index = history.index + 1; michael@0: sendAsyncMessage("Content:SessionHistory", { entries: entries, index: index }); michael@0: }, michael@0: michael@0: _serializeHistoryEntry: function _serializeHistoryEntry(aEntry) { michael@0: let entry = { url: aEntry.URI.spec }; michael@0: michael@0: if (Util.isURLEmpty(entry.url)) { michael@0: entry.title = Util.getEmptyURLTabTitle(); michael@0: } else { michael@0: entry.title = aEntry.title; michael@0: } michael@0: michael@0: if (!(aEntry instanceof Ci.nsISHEntry)) michael@0: return entry; michael@0: michael@0: let cacheKey = aEntry.cacheKey; michael@0: if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 && cacheKey.data != 0) michael@0: entry.cacheKey = cacheKey.data; michael@0: michael@0: entry.ID = aEntry.ID; michael@0: entry.docshellID = aEntry.docshellID; michael@0: michael@0: if (aEntry.referrerURI) michael@0: entry.referrer = aEntry.referrerURI.spec; michael@0: michael@0: if (aEntry.contentType) michael@0: entry.contentType = aEntry.contentType; michael@0: michael@0: let x = {}, y = {}; michael@0: aEntry.getScrollPosition(x, y); michael@0: if (x.value != 0 || y.value != 0) michael@0: entry.scroll = x.value + "," + y.value; michael@0: michael@0: if (aEntry.owner) { michael@0: try { michael@0: let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIObjectOutputStream); michael@0: let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); michael@0: pipe.init(false, false, 0, 0xffffffff, null); michael@0: binaryStream.setOutputStream(pipe.outputStream); michael@0: binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true); michael@0: binaryStream.close(); michael@0: michael@0: // Now we want to read the data from the pipe's input end and encode it. michael@0: let scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream); michael@0: scriptableStream.setInputStream(pipe.inputStream); michael@0: let ownerBytes = scriptableStream.readByteArray(scriptableStream.available()); michael@0: // We can stop doing base64 encoding once our serialization into JSON michael@0: // is guaranteed to handle all chars in strings, including embedded michael@0: // nulls. michael@0: entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes)); michael@0: } catch (e) { dump(e); } michael@0: } michael@0: michael@0: entry.docIdentifier = aEntry.BFCacheEntry.ID; michael@0: michael@0: if (aEntry.stateData != null) { michael@0: entry.structuredCloneState = aEntry.stateData.getDataAsBase64(); michael@0: entry.structuredCloneVersion = aEntry.stateData.formatVersion; michael@0: } michael@0: michael@0: if (!(aEntry instanceof Ci.nsISHContainer)) michael@0: return entry; michael@0: michael@0: if (aEntry.childCount > 0) { michael@0: entry.children = []; michael@0: for (let i = 0; i < aEntry.childCount; i++) { michael@0: let child = aEntry.GetChildAt(i); michael@0: if (child) michael@0: entry.children.push(this._serializeHistoryEntry(child)); michael@0: else // to maintain the correct frame order, insert a dummy entry michael@0: entry.children.push({ url: "about:blank" }); michael@0: michael@0: // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595) michael@0: if (/^wyciwyg:\/\//.test(entry.children[i].url)) { michael@0: delete entry.children; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return entry; michael@0: } michael@0: }; michael@0: michael@0: WebNavigation.init(); michael@0: michael@0: michael@0: let DOMEvents = { michael@0: _timeout: null, michael@0: _sessionEvents: new Set(), michael@0: _sessionEventMap: {"SessionStore:collectFormdata" : FormData.collect, michael@0: "SessionStore:collectScrollPosition" : ScrollPosition.collect}, michael@0: michael@0: init: function() { michael@0: addEventListener("DOMContentLoaded", this, false); michael@0: addEventListener("DOMTitleChanged", this, false); michael@0: addEventListener("DOMLinkAdded", this, false); michael@0: addEventListener("DOMWillOpenModalDialog", this, false); michael@0: addEventListener("DOMModalDialogClosed", this, true); michael@0: addEventListener("DOMWindowClose", this, false); michael@0: addEventListener("DOMPopupBlocked", this, false); michael@0: addEventListener("pageshow", this, false); michael@0: addEventListener("pagehide", this, false); michael@0: michael@0: addEventListener("input", this, true); michael@0: addEventListener("change", this, true); michael@0: addEventListener("scroll", this, true); michael@0: addMessageListener("SessionStore:restoreSessionTabData", this); michael@0: }, michael@0: michael@0: receiveMessage: function(message) { michael@0: switch (message.name) { michael@0: case "SessionStore:restoreSessionTabData": michael@0: if (message.json.formdata) michael@0: FormData.restore(content, message.json.formdata); michael@0: if (message.json.scroll) michael@0: ScrollPosition.restore(content, message.json.scroll.scroll); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: let document = content.document; michael@0: switch (aEvent.type) { michael@0: case "DOMContentLoaded": michael@0: if (document.documentURIObject.spec == "about:blank") michael@0: return; michael@0: michael@0: sendAsyncMessage("DOMContentLoaded", { }); michael@0: michael@0: // Send the session history now too michael@0: WebNavigation.sendHistory(); michael@0: break; michael@0: michael@0: case "pageshow": michael@0: case "pagehide": { michael@0: if (aEvent.target.defaultView != content) michael@0: break; michael@0: michael@0: let util = aEvent.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils); michael@0: michael@0: let json = { michael@0: contentWindowWidth: content.innerWidth, michael@0: contentWindowHeight: content.innerHeight, michael@0: windowId: util.outerWindowID, michael@0: persisted: aEvent.persisted michael@0: }; michael@0: michael@0: // Clear onload focus to prevent the VKB to be shown unexpectingly michael@0: // but only if the location has really changed and not only the michael@0: // fragment identifier michael@0: let contentWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; michael@0: if (!WebProgressListener.hashChanged && contentWindowID == util.currentInnerWindowID) { michael@0: let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); michael@0: focusManager.clearFocus(content); michael@0: } michael@0: michael@0: sendAsyncMessage(aEvent.type, json); michael@0: break; michael@0: } michael@0: michael@0: case "DOMPopupBlocked": { michael@0: let util = aEvent.requestingWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils); michael@0: let json = { michael@0: windowId: util.outerWindowID, michael@0: popupWindowURI: { michael@0: spec: aEvent.popupWindowURI.spec, michael@0: charset: aEvent.popupWindowURI.originCharset michael@0: }, michael@0: popupWindowFeatures: aEvent.popupWindowFeatures, michael@0: popupWindowName: aEvent.popupWindowName michael@0: }; michael@0: michael@0: sendAsyncMessage("DOMPopupBlocked", json); michael@0: break; michael@0: } michael@0: michael@0: case "DOMTitleChanged": michael@0: sendAsyncMessage("DOMTitleChanged", { title: document.title }); michael@0: break; michael@0: michael@0: case "DOMLinkAdded": michael@0: let target = aEvent.originalTarget; michael@0: if (!target.href || target.disabled) michael@0: return; michael@0: michael@0: let json = { michael@0: windowId: target.ownerDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID, michael@0: href: target.href, michael@0: charset: document.characterSet, michael@0: title: target.title, michael@0: rel: target.rel, michael@0: type: target.type michael@0: }; michael@0: michael@0: // rel=icon can also have a sizes attribute michael@0: if (target.hasAttribute("sizes")) michael@0: json.sizes = target.getAttribute("sizes"); michael@0: michael@0: sendAsyncMessage("DOMLinkAdded", json); michael@0: break; michael@0: michael@0: case "DOMWillOpenModalDialog": michael@0: case "DOMModalDialogClosed": michael@0: case "DOMWindowClose": michael@0: let retvals = sendSyncMessage(aEvent.type, { }); michael@0: for (let i in retvals) { michael@0: if (retvals[i].preventDefault) { michael@0: aEvent.preventDefault(); michael@0: break; michael@0: } michael@0: } michael@0: break; michael@0: case "input": michael@0: case "change": michael@0: this._sessionEvents.add("SessionStore:collectFormdata"); michael@0: this._sendUpdates(); michael@0: break; michael@0: case "scroll": michael@0: this._sessionEvents.add("SessionStore:collectScrollPosition"); michael@0: this._sendUpdates(); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: _sendUpdates: function() { michael@0: if (!this._timeout) { michael@0: // Wait a little before sending the message to batch multiple changes. michael@0: this._timeout = setTimeout(function() { michael@0: for (let eventType of this._sessionEvents) { michael@0: sendAsyncMessage(eventType, { michael@0: data: this._sessionEventMap[eventType](content) michael@0: }); michael@0: } michael@0: this._sessionEvents.clear(); michael@0: clearTimeout(this._timeout); michael@0: this._timeout = null; michael@0: }.bind(this), 1000); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: DOMEvents.init(); michael@0: michael@0: let ContentScroll = { michael@0: // The most recent offset set by APZC for the root scroll frame michael@0: _scrollOffset: { x: 0, y: 0 }, michael@0: michael@0: init: function() { michael@0: addMessageListener("Content:SetWindowSize", this); michael@0: michael@0: addEventListener("pagehide", this, false); michael@0: addEventListener("MozScrolledAreaChanged", this, false); michael@0: }, michael@0: michael@0: getScrollOffset: function(aWindow) { michael@0: let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); michael@0: let scrollX = {}, scrollY = {}; michael@0: cwu.getScrollXY(false, scrollX, scrollY); michael@0: return { x: scrollX.value, y: scrollY.value }; michael@0: }, michael@0: michael@0: getScrollOffsetForElement: function(aElement) { michael@0: if (aElement.parentNode == aElement.ownerDocument) michael@0: return this.getScrollOffset(aElement.ownerDocument.defaultView); michael@0: return { x: aElement.scrollLeft, y: aElement.scrollTop }; michael@0: }, michael@0: michael@0: receiveMessage: function(aMessage) { michael@0: let json = aMessage.json; michael@0: switch (aMessage.name) { michael@0: case "Content:SetWindowSize": { michael@0: let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); michael@0: cwu.setCSSViewport(json.width, json.height); michael@0: sendAsyncMessage("Content:SetWindowSize:Complete", {}); michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: handleEvent: function(aEvent) { michael@0: switch (aEvent.type) { michael@0: case "pagehide": michael@0: this._scrollOffset = { x: 0, y: 0 }; michael@0: break; michael@0: michael@0: case "MozScrolledAreaChanged": { michael@0: let doc = aEvent.originalTarget; michael@0: if (content != doc.defaultView) // We are only interested in root scroll pane changes michael@0: return; michael@0: michael@0: sendAsyncMessage("MozScrolledAreaChanged", { michael@0: width: aEvent.width, michael@0: height: aEvent.height, michael@0: left: aEvent.x + content.scrollX michael@0: }); michael@0: michael@0: // Send event only after painting to make sure content views in the parent process have michael@0: // been updated. michael@0: addEventListener("MozAfterPaint", function afterPaint() { michael@0: removeEventListener("MozAfterPaint", afterPaint, false); michael@0: sendAsyncMessage("Content:UpdateDisplayPort"); michael@0: }, false); michael@0: michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: }; michael@0: this.ContentScroll = ContentScroll; michael@0: michael@0: ContentScroll.init(); michael@0: michael@0: let ContentActive = { michael@0: init: function() { michael@0: addMessageListener("Content:Activate", this); michael@0: addMessageListener("Content:Deactivate", this); michael@0: }, michael@0: michael@0: receiveMessage: function(aMessage) { michael@0: let json = aMessage.json; michael@0: switch (aMessage.name) { michael@0: case "Content:Deactivate": michael@0: docShell.isActive = false; michael@0: let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); michael@0: if (json.keepviewport) michael@0: break; michael@0: cwu.setDisplayPortForElement(0, 0, 0, 0, content.document.documentElement, 0); michael@0: break; michael@0: michael@0: case "Content:Activate": michael@0: docShell.isActive = true; michael@0: break; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: ContentActive.init(); michael@0: michael@0: /** michael@0: * Helper class for IndexedDB, child part. Listens using michael@0: * the observer service for events regarding IndexedDB michael@0: * prompts, and sends messages to the parent to actually michael@0: * show the prompts. michael@0: */ michael@0: let IndexedDB = { michael@0: _permissionsPrompt: "indexedDB-permissions-prompt", michael@0: _permissionsResponse: "indexedDB-permissions-response", michael@0: michael@0: _quotaPrompt: "indexedDB-quota-prompt", michael@0: _quotaResponse: "indexedDB-quota-response", michael@0: _quotaCancel: "indexedDB-quota-cancel", michael@0: michael@0: waitingObservers: [], michael@0: michael@0: init: function IndexedDBPromptHelper_init() { michael@0: let os = Services.obs; michael@0: os.addObserver(this, this._permissionsPrompt, false); michael@0: os.addObserver(this, this._quotaPrompt, false); michael@0: os.addObserver(this, this._quotaCancel, false); michael@0: addMessageListener("IndexedDB:Response", this); michael@0: }, michael@0: michael@0: observe: function IndexedDBPromptHelper_observe(aSubject, aTopic, aData) { michael@0: if (aTopic != this._permissionsPrompt && aTopic != this._quotaPrompt && aTopic != this._quotaCancel) { michael@0: throw new Error("Unexpected topic!"); michael@0: } michael@0: michael@0: let requestor = aSubject.QueryInterface(Ci.nsIInterfaceRequestor); michael@0: let observer = requestor.getInterface(Ci.nsIObserver); michael@0: michael@0: let contentWindow = requestor.getInterface(Ci.nsIDOMWindow); michael@0: let contentDocument = contentWindow.document; michael@0: michael@0: if (aTopic == this._quotaCancel) { michael@0: observer.observe(null, this._quotaResponse, Ci.nsIPermissionManager.UNKNOWN_ACTION); michael@0: return; michael@0: } michael@0: michael@0: // Remote to parent michael@0: sendAsyncMessage("IndexedDB:Prompt", { michael@0: topic: aTopic, michael@0: host: contentDocument.documentURIObject.asciiHost, michael@0: location: contentDocument.location.toString(), michael@0: data: aData, michael@0: observerId: this.addWaitingObserver(observer) michael@0: }); michael@0: }, michael@0: michael@0: receiveMessage: function(aMessage) { michael@0: let payload = aMessage.json; michael@0: switch (aMessage.name) { michael@0: case "IndexedDB:Response": michael@0: let observer = this.getAndRemoveWaitingObserver(payload.observerId); michael@0: observer.observe(null, payload.responseTopic, payload.permission); michael@0: } michael@0: }, michael@0: michael@0: addWaitingObserver: function(aObserver) { michael@0: let observerId = 0; michael@0: while (observerId in this.waitingObservers) michael@0: observerId++; michael@0: this.waitingObservers[observerId] = aObserver; michael@0: return observerId; michael@0: }, michael@0: michael@0: getAndRemoveWaitingObserver: function(aObserverId) { michael@0: let observer = this.waitingObservers[aObserverId]; michael@0: delete this.waitingObservers[aObserverId]; michael@0: return observer; michael@0: } michael@0: }; michael@0: michael@0: IndexedDB.init(); michael@0: