// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- let Cc = Components.classes; let Ci = Components.interfaces; dump("!! remote browser loaded\n") let WebProgressListener = { _lastLocation: null, init: function() { let flags = Ci.nsIWebProgress.NOTIFY_LOCATION | Ci.nsIWebProgress.NOTIFY_SECURITY | Ci.nsIWebProgress.NOTIFY_STATE_NETWORK | Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT; let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebProgress); webProgress.addProgressListener(this, flags); }, onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { if (content != aWebProgress.DOMWindow) return; let json = { contentWindowId: content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID, stateFlags: aStateFlags, status: aStatus }; sendAsyncMessage("Content:StateChange", json); }, onProgressChange: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) { }, onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI) { if (content != aWebProgress.DOMWindow) return; let spec = aLocationURI ? aLocationURI.spec : ""; let location = spec.split("#")[0]; let charset = content.document.characterSet; let json = { contentWindowId: content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID, documentURI: aWebProgress.DOMWindow.document.documentURIObject.spec, location: spec, canGoBack: docShell.canGoBack, canGoForward: docShell.canGoForward, charset: charset.toString() }; sendAsyncMessage("Content:LocationChange", json); // Keep track of hash changes this.hashChanged = (location == this._lastLocation); this._lastLocation = location; // When a new page is loaded fire a message for the first paint addEventListener("MozAfterPaint", function(aEvent) { removeEventListener("MozAfterPaint", arguments.callee, true); let scrollOffset = ContentScroll.getScrollOffset(content); sendAsyncMessage("Browser:FirstPaint", scrollOffset); }, true); }, onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) { }, onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) { if (content != aWebProgress.DOMWindow) return; let serialization = SecurityUI.getSSLStatusAsString(); let json = { contentWindowId: content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID, SSLStatusAsString: serialization, state: aState }; sendAsyncMessage("Content:SecurityChange", json); }, QueryInterface: function QueryInterface(aIID) { if (aIID.equals(Ci.nsIWebProgressListener) || aIID.equals(Ci.nsISupportsWeakReference) || aIID.equals(Ci.nsISupports)) { return this; } throw Components.results.NS_ERROR_NO_INTERFACE; } }; WebProgressListener.init(); let SecurityUI = { getSSLStatusAsString: function() { let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus; if (status) { let serhelper = Cc["@mozilla.org/network/serialization-helper;1"] .getService(Ci.nsISerializationHelper); status.QueryInterface(Ci.nsISerializable); return serhelper.serializeToString(status); } return null; } }; let WebNavigation = { _webNavigation: docShell.QueryInterface(Ci.nsIWebNavigation), init: function() { addMessageListener("WebNavigation:GoBack", this); addMessageListener("WebNavigation:GoForward", this); addMessageListener("WebNavigation:GotoIndex", this); addMessageListener("WebNavigation:LoadURI", this); addMessageListener("WebNavigation:Reload", this); addMessageListener("WebNavigation:Stop", this); }, receiveMessage: function(message) { switch (message.name) { case "WebNavigation:GoBack": this.goBack(message); break; case "WebNavigation:GoForward": this.goForward(message); break; case "WebNavigation:GotoIndex": this.gotoIndex(message); break; case "WebNavigation:LoadURI": this.loadURI(message); break; case "WebNavigation:Reload": this.reload(message); break; case "WebNavigation:Stop": this.stop(message); break; } }, goBack: function() { this._webNavigation.goBack(); }, goForward: function() { this._webNavigation.goForward(); }, gotoIndex: function(message) { this._webNavigation.gotoIndex(message.index); }, loadURI: function(message) { let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE; this._webNavigation.loadURI(message.json.uri, flags, null, null, null); }, reload: function(message) { let flags = message.json.flags || this._webNavigation.LOAD_FLAGS_NONE; this._webNavigation.reload(flags); }, stop: function(message) { let flags = message.json.flags || this._webNavigation.STOP_ALL; this._webNavigation.stop(flags); } }; WebNavigation.init(); let DOMEvents = { init: function() { addEventListener("DOMContentLoaded", this, false); addEventListener("DOMTitleChanged", this, false); addEventListener("DOMLinkAdded", this, false); addEventListener("DOMWillOpenModalDialog", this, false); addEventListener("DOMModalDialogClosed", this, true); addEventListener("DOMWindowClose", this, false); addEventListener("DOMPopupBlocked", this, false); addEventListener("pageshow", this, false); addEventListener("pagehide", this, false); }, handleEvent: function(aEvent) { let document = content.document; switch (aEvent.type) { case "DOMContentLoaded": if (document.documentURIObject.spec == "about:blank") return; sendAsyncMessage("DOMContentLoaded", { }); break; case "pageshow": case "pagehide": { if (aEvent.target.defaultView != content) break; let util = aEvent.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); let json = { contentWindowWidth: content.innerWidth, contentWindowHeight: content.innerHeight, windowId: util.outerWindowID, persisted: aEvent.persisted }; // Clear onload focus to prevent the VKB to be shown unexpectingly // but only if the location has really changed and not only the // fragment identifier let contentWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; if (!WebProgressListener.hashChanged && contentWindowID == util.currentInnerWindowID) { let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); focusManager.clearFocus(content); } sendAsyncMessage(aEvent.type, json); break; } case "DOMPopupBlocked": { let util = aEvent.requestingWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); let json = { windowId: util.outerWindowID, popupWindowURI: { spec: aEvent.popupWindowURI.spec, charset: aEvent.popupWindowURI.originCharset }, popupWindowFeatures: aEvent.popupWindowFeatures, popupWindowName: aEvent.popupWindowName }; sendAsyncMessage("DOMPopupBlocked", json); break; } case "DOMTitleChanged": sendAsyncMessage("DOMTitleChanged", { title: document.title }); break; case "DOMLinkAdded": let target = aEvent.originalTarget; if (!target.href || target.disabled) return; let json = { windowId: target.ownerDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID, href: target.href, charset: document.characterSet, title: target.title, rel: target.rel, type: target.type }; sendAsyncMessage("DOMLinkAdded", json); break; case "DOMWillOpenModalDialog": case "DOMModalDialogClosed": case "DOMWindowClose": let retvals = sendSyncMessage(aEvent.type, { }); for (let i in retvals) { if (retvals[i].preventDefault) { aEvent.preventDefault(); break; } } break; } } }; DOMEvents.init(); let ContentScroll = { _scrollOffset: { x: 0, y: 0 }, init: function() { addMessageListener("Content:SetCacheViewport", this); addMessageListener("Content:SetWindowSize", this); addEventListener("scroll", this, false); addEventListener("pagehide", this, false); addEventListener("MozScrolledAreaChanged", this, false); }, getScrollOffset: function(aWindow) { let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); let scrollX = {}, scrollY = {}; cwu.getScrollXY(false, scrollX, scrollY); return { x: scrollX.value, y: scrollY.value }; }, getScrollOffsetForElement: function(aElement) { if (aElement.parentNode == aElement.ownerDocument) return this.getScrollOffset(aElement.ownerDocument.defaultView); return { x: aElement.scrollLeft, y: aElement.scrollTop }; }, setScrollOffsetForElement: function(aElement, aLeft, aTop) { if (aElement.parentNode == aElement.ownerDocument) { aElement.ownerDocument.defaultView.scrollTo(aLeft, aTop); } else { aElement.scrollLeft = aLeft; aElement.scrollTop = aTop; } }, receiveMessage: function(aMessage) { let json = aMessage.json; switch (aMessage.name) { case "Content:SetCacheViewport": { // Set resolution for root view let rootCwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); if (json.id == 1) rootCwu.setResolution(json.scale, json.scale); let displayport = new Rect(json.x, json.y, json.w, json.h); if (displayport.isEmpty()) break; // Map ID to element let element = rootCwu.findElementWithViewId(json.id); if (!element) break; // Set the scroll offset for this element if specified if (json.scrollX >= 0 && json.scrollY >= 0) { this.setScrollOffsetForElement(element, json.scrollX, json.scrollY) if (json.id == 1) this._scrollOffset = this.getScrollOffset(content); } // Set displayport. We want to set this after setting the scroll offset, because // it is calculated based on the scroll offset. let scrollOffset = this.getScrollOffsetForElement(element); let x = displayport.x - scrollOffset.x; let y = displayport.y - scrollOffset.y; if (json.id == 1) { x = Math.round(x * json.scale) / json.scale; y = Math.round(y * json.scale) / json.scale; } let win = element.ownerDocument.defaultView; let winCwu = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); winCwu.setDisplayPortForElement(x, y, displayport.width, displayport.height, element); // XXX If we scrolled during this displayport update, then it is the // end of a pan. Due to bug 637852, there may be seaming issues // with the visible content, so we need to redraw. if (json.id == 1 && json.scrollX >= 0 && json.scrollY >= 0) win.setTimeout( function() { winCwu.redraw(); }, 0); break; } case "Content:SetWindowSize": { let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); cwu.setCSSViewport(json.width, json.height); break; } } }, handleEvent: function(aEvent) { switch (aEvent.type) { case "pagehide": this._scrollOffset = { x: 0, y: 0 }; break; case "scroll": { let doc = aEvent.target; if (doc != content.document) break; this.sendScroll(); break; } case "MozScrolledAreaChanged": { let doc = aEvent.originalTarget; if (content != doc.defaultView) // We are only interested in root scroll pane changes return; // Adjust width and height from the incoming event properties so that we // ignore changes to width and height contributed by growth in page // quadrants other than x > 0 && y > 0. let scrollOffset = this.getScrollOffset(content); let x = aEvent.x + scrollOffset.x; let y = aEvent.y + scrollOffset.y; let width = aEvent.width + (x < 0 ? x : 0); let height = aEvent.height + (y < 0 ? y : 0); sendAsyncMessage("MozScrolledAreaChanged", { width: width, height: height }); break; } } }, sendScroll: function sendScroll() { let scrollOffset = this.getScrollOffset(content); if (this._scrollOffset.x == scrollOffset.x && this._scrollOffset.y == scrollOffset.y) return; this._scrollOffset = scrollOffset; sendAsyncMessage("scroll", scrollOffset); } }; ContentScroll.init(); let ContentActive = { init: function() { addMessageListener("Content:Activate", this); addMessageListener("Content:Deactivate", this); }, receiveMessage: function(aMessage) { let json = aMessage.json; switch (aMessage.name) { case "Content:Deactivate": docShell.isActive = false; let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); cwu.setDisplayPortForElement(0,0,0,0,content.document.documentElement); break; case "Content:Activate": docShell.isActive = true; break; } } }; ContentActive.init();