diff options
Diffstat (limited to 'components/global/content/browser-child.js')
-rw-r--r-- | components/global/content/browser-child.js | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/components/global/content/browser-child.js b/components/global/content/browser-child.js new file mode 100644 index 000000000..ffb07dde2 --- /dev/null +++ b/components/global/content/browser-child.js @@ -0,0 +1,598 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; + +Cu.import("resource://gre/modules/BrowserUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import("resource://gre/modules/RemoteAddonsChild.jsm"); +Cu.import("resource://gre/modules/Timer.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PageThumbUtils", + "resource://gre/modules/PageThumbUtils.jsm"); + +function makeInputStream(aString) { + let stream = Cc["@mozilla.org/io/string-input-stream;1"]. + createInstance(Ci.nsISupportsCString); + stream.data = aString; + return stream; // XPConnect will QI this to nsIInputStream for us. +} + +var WebProgressListener = { + init: function() { + this._filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"] + .createInstance(Ci.nsIWebProgress); + this._filter.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL); + + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + webProgress.addProgressListener(this._filter, Ci.nsIWebProgress.NOTIFY_ALL); + }, + + uninit() { + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + webProgress.removeProgressListener(this._filter); + + this._filter.removeProgressListener(this); + this._filter = null; + }, + + _requestSpec: function (aRequest, aPropertyName) { + if (!aRequest || !(aRequest instanceof Ci.nsIChannel)) + return null; + return aRequest.QueryInterface(Ci.nsIChannel)[aPropertyName].spec; + }, + + _setupJSON: function setupJSON(aWebProgress, aRequest) { + let innerWindowID = null; + if (aWebProgress) { + let domWindowID = null; + try { + let utils = aWebProgress.DOMWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + domWindowID = utils.outerWindowID; + innerWindowID = utils.currentInnerWindowID; + } catch (e) { + // If nsDocShell::Destroy has already been called, then we'll + // get NS_NOINTERFACE when trying to get the DOM window. + // If there is no current inner window, we'll get + // NS_ERROR_NOT_AVAILABLE. + } + + aWebProgress = { + isTopLevel: aWebProgress.isTopLevel, + isLoadingDocument: aWebProgress.isLoadingDocument, + loadType: aWebProgress.loadType, + DOMWindowID: domWindowID + }; + } + + return { + webProgress: aWebProgress || null, + requestURI: this._requestSpec(aRequest, "URI"), + originalRequestURI: this._requestSpec(aRequest, "originalURI"), + documentContentType: content.document && content.document.contentType, + innerWindowID, + }; + }, + + _setupObjects: function setupObjects(aWebProgress, aRequest) { + let domWindow; + try { + domWindow = aWebProgress && aWebProgress.DOMWindow; + } catch (e) { + // If nsDocShell::Destroy has already been called, then we'll + // get NS_NOINTERFACE when trying to get the DOM window. Ignore + // that here. + domWindow = null; + } + + return { + contentWindow: content, + // DOMWindow is not necessarily the content-window with subframes. + DOMWindow: domWindow, + webProgress: aWebProgress, + request: aRequest, + }; + }, + + _send(name, data, objects) { + if (RemoteAddonsChild.useSyncWebProgress) { + sendRpcMessage(name, data, objects); + } else { + sendAsyncMessage(name, data, objects); + } + }, + + onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { + let json = this._setupJSON(aWebProgress, aRequest); + let objects = this._setupObjects(aWebProgress, aRequest); + + json.stateFlags = aStateFlags; + json.status = aStatus; + + // It's possible that this state change was triggered by + // loading an internal error page, for which the parent + // will want to know some details, so we'll update it with + // the documentURI. + if (aWebProgress && aWebProgress.isTopLevel) { + json.documentURI = content.document.documentURIObject.spec; + json.charset = content.document.characterSet; + json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu; + json.inLoadURI = WebNavigation.inLoadURI; + } + + this._send("Content:StateChange", json, objects); + }, + + onProgressChange: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) { + let json = this._setupJSON(aWebProgress, aRequest); + let objects = this._setupObjects(aWebProgress, aRequest); + + json.curSelf = aCurSelf; + json.maxSelf = aMaxSelf; + json.curTotal = aCurTotal; + json.maxTotal = aMaxTotal; + + this._send("Content:ProgressChange", json, objects); + }, + + onProgressChange64: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) { + this.onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal); + }, + + onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI, aFlags) { + let json = this._setupJSON(aWebProgress, aRequest); + let objects = this._setupObjects(aWebProgress, aRequest); + + json.location = aLocationURI ? aLocationURI.spec : ""; + json.flags = aFlags; + + // These properties can change even for a sub-frame navigation. + let webNav = docShell.QueryInterface(Ci.nsIWebNavigation); + json.canGoBack = webNav.canGoBack; + json.canGoForward = webNav.canGoForward; + + if (aWebProgress && aWebProgress.isTopLevel) { + json.documentURI = content.document.documentURIObject.spec; + json.title = content.document.title; + json.charset = content.document.characterSet; + json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu; + json.principal = content.document.nodePrincipal; + json.synthetic = content.document.mozSyntheticDocument; + json.inLoadURI = WebNavigation.inLoadURI; + } + + this._send("Content:LocationChange", json, objects); + }, + + onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) { + let json = this._setupJSON(aWebProgress, aRequest); + let objects = this._setupObjects(aWebProgress, aRequest); + + json.status = aStatus; + json.message = aMessage; + + this._send("Content:StatusChange", json, objects); + }, + + onSecurityChange: function onSecurityChange(aWebProgress, aRequest, aState) { + let json = this._setupJSON(aWebProgress, aRequest); + let objects = this._setupObjects(aWebProgress, aRequest); + + json.state = aState; + json.status = SecurityUI.getSSLStatusAsString(); + + this._send("Content:SecurityChange", json, objects); + }, + + onRefreshAttempted: function onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) { + return true; + }, + + sendLoadCallResult() { + sendAsyncMessage("Content:LoadURIResult"); + }, + + QueryInterface: function QueryInterface(aIID) { + if (aIID.equals(Ci.nsIWebProgressListener) || + aIID.equals(Ci.nsIWebProgressListener2) || + aIID.equals(Ci.nsISupportsWeakReference) || + aIID.equals(Ci.nsISupports)) { + return this; + } + + throw Components.results.NS_ERROR_NO_INTERFACE; + } +}; + +WebProgressListener.init(); +addEventListener("unload", () => { + WebProgressListener.uninit(); +}); + +var WebNavigation = { + init: function() { + addMessageListener("WebNavigation:GoBack", this); + addMessageListener("WebNavigation:GoForward", this); + addMessageListener("WebNavigation:GotoIndex", this); + addMessageListener("WebNavigation:LoadURI", this); + addMessageListener("WebNavigation:SetOriginAttributes", this); + addMessageListener("WebNavigation:Reload", this); + addMessageListener("WebNavigation:Stop", this); + }, + + get webNavigation() { + return docShell.QueryInterface(Ci.nsIWebNavigation); + }, + + _inLoadURI: false, + + get inLoadURI() { + return this._inLoadURI; + }, + + receiveMessage: function(message) { + switch (message.name) { + case "WebNavigation:GoBack": + this.goBack(); + break; + case "WebNavigation:GoForward": + this.goForward(); + break; + case "WebNavigation:GotoIndex": + this.gotoIndex(message.data.index); + break; + case "WebNavigation:LoadURI": + this.loadURI(message.data.uri, message.data.flags, + message.data.referrer, message.data.referrerPolicy, + message.data.postData, message.data.headers, + message.data.baseURI); + break; + case "WebNavigation:SetOriginAttributes": + this.setOriginAttributes(message.data.originAttributes); + break; + case "WebNavigation:Reload": + this.reload(message.data.flags); + break; + case "WebNavigation:Stop": + this.stop(message.data.flags); + break; + } + }, + + _wrapURIChangeCall(fn) { + this._inLoadURI = true; + try { + fn(); + } finally { + this._inLoadURI = false; + WebProgressListener.sendLoadCallResult(); + } + }, + + goBack: function() { + if (this.webNavigation.canGoBack) { + this._wrapURIChangeCall(() => this.webNavigation.goBack()); + } + }, + + goForward: function() { + if (this.webNavigation.canGoForward) { + this._wrapURIChangeCall(() => this.webNavigation.goForward()); + } + }, + + gotoIndex: function(index) { + this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(index)); + }, + + loadURI: function(uri, flags, referrer, referrerPolicy, postData, headers, baseURI) { + if (referrer) + referrer = Services.io.newURI(referrer, null, null); + if (postData) + postData = makeInputStream(postData); + if (headers) + headers = makeInputStream(headers); + if (baseURI) + baseURI = Services.io.newURI(baseURI, null, null); + this._wrapURIChangeCall(() => { + return this.webNavigation.loadURIWithOptions(uri, flags, referrer, referrerPolicy, + postData, headers, baseURI); + }); + }, + + setOriginAttributes: function(originAttributes) { + if (originAttributes) { + this.webNavigation.setOriginAttributesBeforeLoading(originAttributes); + } + }, + + reload: function(flags) { + this.webNavigation.reload(flags); + }, + + stop: function(flags) { + this.webNavigation.stop(flags); + } +}; + +WebNavigation.init(); + +var SecurityUI = { + getSSLStatusAsString: function() { + let status = docShell.securityUI.QueryInterface(Ci.nsISSLStatusProvider).SSLStatus; + + if (status) { + let helper = Cc["@mozilla.org/network/serialization-helper;1"] + .getService(Ci.nsISerializationHelper); + + status.QueryInterface(Ci.nsISerializable); + return helper.serializeToString(status); + } + + return null; + } +}; + +var ControllerCommands = { + init: function () { + addMessageListener("ControllerCommands:Do", this); + addMessageListener("ControllerCommands:DoWithParams", this); + }, + + receiveMessage: function(message) { + switch (message.name) { + case "ControllerCommands:Do": + if (docShell.isCommandEnabled(message.data)) + docShell.doCommand(message.data); + break; + + case "ControllerCommands:DoWithParams": + var data = message.data; + if (docShell.isCommandEnabled(data.cmd)) { + var params = Cc["@mozilla.org/embedcomp/command-params;1"]. + createInstance(Ci.nsICommandParams); + for (var name in data.params) { + var value = data.params[name]; + if (value.type == "long") { + params.setLongValue(name, parseInt(value.value)); + } else { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + } + } + docShell.doCommandWithParams(data.cmd, params); + } + break; + } + } +} + +ControllerCommands.init() + +addEventListener("DOMTitleChanged", function (aEvent) { + let document = content.document; + switch (aEvent.type) { + case "DOMTitleChanged": + if (!aEvent.isTrusted || aEvent.target.defaultView != content) + return; + + sendAsyncMessage("DOMTitleChanged", { title: document.title }); + break; + } +}, false); + +addEventListener("DOMWindowClose", function (aEvent) { + if (!aEvent.isTrusted) + return; + sendAsyncMessage("DOMWindowClose"); +}, false); + +addEventListener("ImageContentLoaded", function (aEvent) { + if (content.document instanceof Ci.nsIImageDocument) { + let req = content.document.imageRequest; + if (!req.image) + return; + sendAsyncMessage("ImageDocumentLoaded", { width: req.image.width, + height: req.image.height }); + } +}, false); + +const ZoomManager = { + get fullZoom() { + return this._cache.fullZoom; + }, + + get textZoom() { + return this._cache.textZoom; + }, + + set fullZoom(value) { + this._cache.fullZoom = value; + this._markupViewer.fullZoom = value; + }, + + set textZoom(value) { + this._cache.textZoom = value; + this._markupViewer.textZoom = value; + }, + + refreshFullZoom: function() { + return this._refreshZoomValue('fullZoom'); + }, + + refreshTextZoom: function() { + return this._refreshZoomValue('textZoom'); + }, + + /** + * Retrieves specified zoom property value from markupViewer and refreshes + * cache if needed. + * @param valueName Either 'fullZoom' or 'textZoom'. + * @returns Returns true if cached value was actually refreshed. + * @private + */ + _refreshZoomValue: function(valueName) { + let actualZoomValue = this._markupViewer[valueName]; + // Round to remove any floating-point error. + actualZoomValue = Number(actualZoomValue.toFixed(2)); + if (actualZoomValue != this._cache[valueName]) { + this._cache[valueName] = actualZoomValue; + return true; + } + return false; + }, + + get _markupViewer() { + return docShell.contentViewer; + }, + + _cache: { + fullZoom: NaN, + textZoom: NaN + } +}; + +addMessageListener("FullZoom", function (aMessage) { + ZoomManager.fullZoom = aMessage.data.value; +}); + +addMessageListener("TextZoom", function (aMessage) { + ZoomManager.textZoom = aMessage.data.value; +}); + +addEventListener("FullZoomChange", function () { + if (ZoomManager.refreshFullZoom()) { + sendAsyncMessage("FullZoomChange", { value: ZoomManager.fullZoom }); + } +}, false); + +addEventListener("TextZoomChange", function (aEvent) { + if (ZoomManager.refreshTextZoom()) { + sendAsyncMessage("TextZoomChange", { value: ZoomManager.textZoom }); + } +}, false); + +addEventListener("ZoomChangeUsingMouseWheel", function () { + sendAsyncMessage("ZoomChangeUsingMouseWheel", {}); +}, false); + +addMessageListener("UpdateCharacterSet", function (aMessage) { + docShell.charset = aMessage.data.value; + docShell.gatherCharsetMenuTelemetry(); +}); + +/** + * Remote thumbnail request handler for PageThumbs thumbnails. + */ +addMessageListener("Browser:Thumbnail:Request", function (aMessage) { + let snapshot; + let args = aMessage.data.additionalArgs; + let fullScale = args ? args.fullScale : false; + if (fullScale) { + snapshot = PageThumbUtils.createSnapshotThumbnail(content, null, args); + } else { + let snapshotWidth = aMessage.data.canvasWidth; + let snapshotHeight = aMessage.data.canvasHeight; + snapshot = + PageThumbUtils.createCanvas(content, snapshotWidth, snapshotHeight); + PageThumbUtils.createSnapshotThumbnail(content, snapshot, args); + } + + snapshot.toBlob(function (aBlob) { + sendAsyncMessage("Browser:Thumbnail:Response", { + thumbnail: aBlob, + id: aMessage.data.id + }); + }); +}); + +/** + * Remote isSafeForCapture request handler for PageThumbs. + */ +addMessageListener("Browser:Thumbnail:CheckState", function (aMessage) { + let result = PageThumbUtils.shouldStoreContentThumbnail(content, docShell); + sendAsyncMessage("Browser:Thumbnail:CheckState:Response", { + result: result + }); +}); + +/** + * Remote GetOriginalURL request handler for PageThumbs. + */ +addMessageListener("Browser:Thumbnail:GetOriginalURL", function (aMessage) { + let channel = docShell.currentDocumentChannel; + let channelError = PageThumbUtils.isChannelErrorResponse(channel); + let originalURL; + try { + originalURL = channel.originalURI.spec; + } catch (ex) {} + sendAsyncMessage("Browser:Thumbnail:GetOriginalURL:Response", { + channelError: channelError, + originalURL: originalURL, + }); +}); + +/** + * Remote createAboutBlankContentViewer request handler. + */ +addMessageListener("Browser:CreateAboutBlank", function(aMessage) { + if (!content.document || content.document.documentURI != "about:blank") { + throw new Error("Can't create a content viewer unless on about:blank"); + } + let principal = aMessage.data; + principal = BrowserUtils.principalWithMatchingOA(principal, content.document.nodePrincipal); + docShell.createAboutBlankContentViewer(principal); +}); + +// The AddonsChild needs to be rooted so that it stays alive as long as +// the tab. +var AddonsChild = RemoteAddonsChild.init(this); +if (AddonsChild) { + addEventListener("unload", () => { + RemoteAddonsChild.uninit(AddonsChild); + }); +} + +addMessageListener("NetworkPrioritizer:AdjustPriority", (msg) => { + let webNav = docShell.QueryInterface(Ci.nsIWebNavigation); + let loadGroup = webNav.QueryInterface(Ci.nsIDocumentLoader) + .loadGroup.QueryInterface(Ci.nsISupportsPriority); + loadGroup.adjustPriority(msg.data.adjustment); +}); + +addMessageListener("NetworkPrioritizer:SetPriority", (msg) => { + let webNav = docShell.QueryInterface(Ci.nsIWebNavigation); + let loadGroup = webNav.QueryInterface(Ci.nsIDocumentLoader) + .loadGroup.QueryInterface(Ci.nsISupportsPriority); + loadGroup.priority = msg.data.priority; +}); + +addMessageListener("InPermitUnload", msg => { + let inPermitUnload = docShell.contentViewer && docShell.contentViewer.inPermitUnload; + sendAsyncMessage("InPermitUnload", {id: msg.data.id, inPermitUnload}); +}); + +addMessageListener("PermitUnload", msg => { + sendAsyncMessage("PermitUnload", {id: msg.data.id, kind: "start"}); + + let permitUnload = true; + if (docShell && docShell.contentViewer) { + permitUnload = docShell.contentViewer.permitUnload(); + } + + sendAsyncMessage("PermitUnload", {id: msg.data.id, kind: "end", permitUnload}); +}); + +// We may not get any responses to Browser:Init if the browser element +// is torn down too quickly. +var outerWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .outerWindowID; +sendAsyncMessage("Browser:Init", {outerWindowID: outerWindowID}); |