diff options
Diffstat (limited to 'toolkit/modules/RemoteWebProgress.jsm')
-rw-r--r-- | toolkit/modules/RemoteWebProgress.jsm | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/toolkit/modules/RemoteWebProgress.jsm b/toolkit/modules/RemoteWebProgress.jsm new file mode 100644 index 000000000..0031d6b98 --- /dev/null +++ b/toolkit/modules/RemoteWebProgress.jsm @@ -0,0 +1,285 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- +// 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/. + +this.EXPORTED_SYMBOLS = ["RemoteWebProgressManager"]; + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +function newURI(spec) +{ + return Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService) + .newURI(spec, null, null); +} + +function RemoteWebProgressRequest(spec, originalSpec, requestCPOW) +{ + this.wrappedJSObject = this; + + this._uri = newURI(spec); + this._originalURI = newURI(originalSpec); + this._requestCPOW = requestCPOW; +} + +RemoteWebProgressRequest.prototype = { + QueryInterface : XPCOMUtils.generateQI([Ci.nsIChannel]), + + get URI() { return this._uri.clone(); }, + get originalURI() { return this._originalURI.clone(); } +}; + +function RemoteWebProgress(aManager, aIsTopLevel) { + this.wrappedJSObject = this; + + this._manager = aManager; + + this._isLoadingDocument = false; + this._DOMWindow = null; + this._DOMWindowID = 0; + this._isTopLevel = aIsTopLevel; + this._loadType = 0; +} + +RemoteWebProgress.prototype = { + NOTIFY_STATE_REQUEST: 0x00000001, + NOTIFY_STATE_DOCUMENT: 0x00000002, + NOTIFY_STATE_NETWORK: 0x00000004, + NOTIFY_STATE_WINDOW: 0x00000008, + NOTIFY_STATE_ALL: 0x0000000f, + NOTIFY_PROGRESS: 0x00000010, + NOTIFY_STATUS: 0x00000020, + NOTIFY_SECURITY: 0x00000040, + NOTIFY_LOCATION: 0x00000080, + NOTIFY_REFRESH: 0x00000100, + NOTIFY_ALL: 0x000001ff, + + get isLoadingDocument() { return this._isLoadingDocument }, + get DOMWindow() { return this._DOMWindow; }, + get DOMWindowID() { return this._DOMWindowID; }, + get isTopLevel() { return this._isTopLevel }, + get loadType() { return this._loadType; }, + + addProgressListener: function (aListener) { + this._manager.addProgressListener(aListener); + }, + + removeProgressListener: function (aListener) { + this._manager.removeProgressListener(aListener); + } +}; + +function RemoteWebProgressManager (aBrowser) { + this._topLevelWebProgress = new RemoteWebProgress(this, true); + this._progressListeners = []; + + this.swapBrowser(aBrowser); +} + +RemoteWebProgressManager.argumentsForAddonListener = function(kind, args) { + function checkType(arg, typ) { + if (!arg) { + return false; + } + return (arg instanceof typ) || + (arg instanceof Ci.nsISupports && arg.wrappedJSObject instanceof typ); + } + + // Arguments for a tabs listener are shifted over one since the + // <browser> element is passed as the first argument. + let webProgressIndex = 0; + let requestIndex = 1; + if (kind == "tabs") { + webProgressIndex = 1; + requestIndex = 2; + } + + if (checkType(args[webProgressIndex], RemoteWebProgress)) { + args[webProgressIndex] = args[webProgressIndex].wrappedJSObject._webProgressCPOW; + } + + if (checkType(args[requestIndex], RemoteWebProgressRequest)) { + args[requestIndex] = args[requestIndex].wrappedJSObject._requestCPOW; + } + + return args; +}; + +RemoteWebProgressManager.prototype = { + swapBrowser: function(aBrowser) { + if (this._messageManager) { + this._messageManager.removeMessageListener("Content:StateChange", this); + this._messageManager.removeMessageListener("Content:LocationChange", this); + this._messageManager.removeMessageListener("Content:SecurityChange", this); + this._messageManager.removeMessageListener("Content:StatusChange", this); + this._messageManager.removeMessageListener("Content:ProgressChange", this); + this._messageManager.removeMessageListener("Content:LoadURIResult", this); + } + + this._browser = aBrowser; + this._messageManager = aBrowser.messageManager; + this._messageManager.addMessageListener("Content:StateChange", this); + this._messageManager.addMessageListener("Content:LocationChange", this); + this._messageManager.addMessageListener("Content:SecurityChange", this); + this._messageManager.addMessageListener("Content:StatusChange", this); + this._messageManager.addMessageListener("Content:ProgressChange", this); + this._messageManager.addMessageListener("Content:LoadURIResult", this); + }, + + get topLevelWebProgress() { + return this._topLevelWebProgress; + }, + + addProgressListener: function (aListener) { + let listener = aListener.QueryInterface(Ci.nsIWebProgressListener); + this._progressListeners.push(listener); + }, + + removeProgressListener: function (aListener) { + this._progressListeners = + this._progressListeners.filter(l => l != aListener); + }, + + _fixSSLStatusAndState: function (aStatus, aState) { + let deserialized = null; + if (aStatus) { + let helper = Cc["@mozilla.org/network/serialization-helper;1"] + .getService(Components.interfaces.nsISerializationHelper); + + deserialized = helper.deserializeObject(aStatus) + deserialized.QueryInterface(Ci.nsISSLStatus); + } + + return [deserialized, aState]; + }, + + setCurrentURI: function (aURI) { + // This function is simpler than nsDocShell::SetCurrentURI since + // it doesn't have to deal with child docshells. + let remoteWebNav = this._browser._remoteWebNavigationImpl; + remoteWebNav._currentURI = aURI; + + let webProgress = this.topLevelWebProgress; + for (let p of this._progressListeners) { + p.onLocationChange(webProgress, null, aURI); + } + }, + + _callProgressListeners: function(methodName, ...args) { + for (let p of this._progressListeners) { + if (p[methodName]) { + try { + p[methodName].apply(p, args); + } catch (ex) { + Cu.reportError("RemoteWebProgress failed to call " + methodName + ": " + ex + "\n"); + } + } + } + }, + + receiveMessage: function (aMessage) { + let json = aMessage.json; + let objects = aMessage.objects; + // This message is a custom one we send as a result of a loadURI call. + // It shouldn't go through the same processing as all the forwarded + // webprogresslistener messages. + if (aMessage.name == "Content:LoadURIResult") { + this._browser.inLoadURI = false; + return; + } + + let webProgress = null; + let isTopLevel = json.webProgress && json.webProgress.isTopLevel; + // The top-level WebProgress is always the same, but because we don't + // really have a concept of subframes/content we always create a new object + // for those. + if (json.webProgress) { + webProgress = isTopLevel ? this._topLevelWebProgress + : new RemoteWebProgress(this, false); + + // Update the actual WebProgress fields. + webProgress._isLoadingDocument = json.webProgress.isLoadingDocument; + webProgress._DOMWindow = objects.DOMWindow; + webProgress._DOMWindowID = json.webProgress.DOMWindowID; + webProgress._loadType = json.webProgress.loadType; + webProgress._webProgressCPOW = objects.webProgress; + } + + // The WebProgressRequest object however is always dynamic. + let request = null; + if (json.requestURI) { + request = new RemoteWebProgressRequest(json.requestURI, + json.originalRequestURI, + objects.request); + } + + if (isTopLevel) { + this._browser._contentWindow = objects.contentWindow; + this._browser._documentContentType = json.documentContentType; + if (typeof json.inLoadURI != "undefined") { + this._browser.inLoadURI = json.inLoadURI; + } + if (json.charset) { + this._browser._characterSet = json.charset; + this._browser._mayEnableCharacterEncodingMenu = json.mayEnableCharacterEncodingMenu; + } + } + + switch (aMessage.name) { + case "Content:StateChange": + if (isTopLevel) { + this._browser._documentURI = newURI(json.documentURI); + } + this._callProgressListeners("onStateChange", webProgress, request, json.stateFlags, json.status); + break; + + case "Content:LocationChange": + let location = newURI(json.location); + let flags = json.flags; + let remoteWebNav = this._browser._remoteWebNavigationImpl; + + // These properties can change even for a sub-frame navigation. + remoteWebNav.canGoBack = json.canGoBack; + remoteWebNav.canGoForward = json.canGoForward; + + if (isTopLevel) { + remoteWebNav._currentURI = location; + this._browser._documentURI = newURI(json.documentURI); + this._browser._contentTitle = json.title; + this._browser._imageDocument = null; + this._browser._contentPrincipal = json.principal; + this._browser._isSyntheticDocument = json.synthetic; + this._browser._innerWindowID = json.innerWindowID; + } + + this._callProgressListeners("onLocationChange", webProgress, request, location, flags); + break; + + case "Content:SecurityChange": + let [status, state] = this._fixSSLStatusAndState(json.status, json.state); + + if (isTopLevel) { + // Invoking this getter triggers the generation of the underlying object, + // which we need to access with ._securityUI, because .securityUI returns + // a wrapper that makes _update inaccessible. + void this._browser.securityUI; + this._browser._securityUI._update(status, state); + } + + this._callProgressListeners("onSecurityChange", webProgress, request, state); + break; + + case "Content:StatusChange": + this._callProgressListeners("onStatusChange", webProgress, request, json.status, json.message); + break; + + case "Content:ProgressChange": + this._callProgressListeners("onProgressChange", webProgress, request, json.curSelf, json.maxSelf, json.curTotal, json.maxTotal); + break; + } + }, +}; |