gecko-dev/toolkit/modules/RemoteWebProgress.jsm

304 lines
10 KiB
JavaScript

// -*- 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);
}
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(aListener, aNotifyMask) {
this._manager.addProgressListener(aListener, aNotifyMask);
},
removeProgressListener(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(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(aListener, aNotifyMask) {
let listener = aListener.QueryInterface(Ci.nsIWebProgressListener);
this._progressListeners.push({
listener,
mask: aNotifyMask || Ci.nsIWebProgress.NOTIFY_ALL
});
},
removeProgressListener(aListener) {
this._progressListeners =
this._progressListeners.filter(l => l.listener != aListener);
},
_fixSSLStatusAndState(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(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 { listener, mask } of this._progressListeners) {
if (mask & Ci.nsIWebProgress.NOTIFY_LOCATION) {
listener.onLocationChange(webProgress, null, aURI);
}
}
},
_callProgressListeners(type, methodName, ...args) {
for (let { listener, mask } of this._progressListeners) {
if ((mask & type) && listener[methodName]) {
try {
listener[methodName].apply(listener, args);
} catch (ex) {
Cu.reportError("RemoteWebProgress failed to call " + methodName + ": " + ex + "\n");
}
}
}
},
receiveMessage(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(
Ci.nsIWebProgress.NOTIFY_STATE_ALL, "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(
Ci.nsIWebProgress.NOTIFY_LOCATION, "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(
Ci.nsIWebProgress.NOTIFY_SECURITY, "onSecurityChange", webProgress,
request, state
);
break;
case "Content:StatusChange":
this._callProgressListeners(
Ci.nsIWebProgress.NOTIFY_STATUS, "onStatusChange", webProgress, request,
json.status, json.message
);
break;
case "Content:ProgressChange":
this._callProgressListeners(
Ci.nsIWebProgress.NOTIFY_PROGRESS, "onProgressChange", webProgress,
request, json.curSelf, json.maxSelf, json.curTotal, json.maxTotal
);
break;
}
},
};