gecko-dev/toolkit/content/widgets/remote-browser.xml
Bill McCloskey 0736c916d0 Bug 1412456 - Send document CPOW as well as window CPOW (r=mconley)
Currently, if you try to use contentDocumentAsCPOW, you'll get an
exception saying that browser code is not allowed to use CPOWs. That's
because we cleverly tried to get the CPOW via
contentWindowAsCPOW.document. However, this property access happens
inside remote-browser.xul, where CPOWs are forbidden. So it doesn't
work.

MozReview-Commit-ID: ANWad4tvGpU
2017-12-07 12:55:19 -08:00

593 lines
21 KiB
XML

<?xml version="1.0"?>
<!-- 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/. -->
<bindings id="firefoxBrowserBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="remote-browser" extends="chrome://global/content/bindings/browser.xml#browser">
<implementation type="application/javascript"
implements="nsIObserver, nsIDOMEventListener, nsIMessageListener, nsIRemoteBrowser">
<field name="_securityUI">null</field>
<property name="securityUI"
readonly="true">
<getter><![CDATA[
if (!this._securityUI) {
// Don't attempt to create the remote web progress if the
// messageManager has already gone away
if (!this.messageManager)
return null;
let jsm = "resource://gre/modules/RemoteSecurityUI.jsm";
let RemoteSecurityUI = Components.utils.import(jsm, {}).RemoteSecurityUI;
this._securityUI = new RemoteSecurityUI();
}
// We want to double-wrap the JS implemented interface, so that QI and instanceof works.
var ptr = Components.classes["@mozilla.org/supports-interface-pointer;1"]
.createInstance(Components.interfaces.nsISupportsInterfacePointer);
ptr.data = this._securityUI;
return ptr.data.QueryInterface(Components.interfaces.nsISecureBrowserUI);
]]></getter>
</property>
<field name="_controller">null</field>
<field name="_selectParentHelper">null</field>
<field name="_remoteWebNavigation">null</field>
<property name="webNavigation"
onget="return this._remoteWebNavigation;"
readonly="true"/>
<field name="_remoteWebProgress">null</field>
<property name="webProgress" readonly="true">
<getter>
<![CDATA[
if (!this._remoteWebProgress) {
// Don't attempt to create the remote web progress if the
// messageManager has already gone away
if (!this.messageManager)
return null;
let jsm = "resource://gre/modules/RemoteWebProgress.jsm";
let { RemoteWebProgressManager } = Components.utils.import(jsm, {});
this._remoteWebProgressManager = new RemoteWebProgressManager(this);
this._remoteWebProgress = this._remoteWebProgressManager.topLevelWebProgress;
}
return this._remoteWebProgress;
]]>
</getter>
</property>
<field name="_remoteFinder">null</field>
<property name="finder" readonly="true">
<getter><![CDATA[
if (!this._remoteFinder) {
// Don't attempt to create the remote finder if the
// messageManager has already gone away
if (!this.messageManager)
return null;
let jsm = "resource://gre/modules/RemoteFinder.jsm";
let { RemoteFinder } = Components.utils.import(jsm, {});
this._remoteFinder = new RemoteFinder(this);
}
return this._remoteFinder;
]]></getter>
</property>
<field name="_documentURI">null</field>
<field name="_documentContentType">null</field>
<!--
Used by session restore to ensure that currentURI is set so
that switch-to-tab works before the tab is fully
restored. This function also invokes onLocationChanged
listeners in tabbrowser.xml.
-->
<method name="_setCurrentURI">
<parameter name="aURI"/>
<body><![CDATA[
this._remoteWebProgressManager.setCurrentURI(aURI);
]]></body>
</method>
<property name="documentURI"
onget="return this._documentURI;"
readonly="true"/>
<property name="documentContentType"
onget="return this._documentContentType;"
readonly="true"/>
<field name="_contentTitle">""</field>
<property name="contentTitle"
onget="return this._contentTitle"
readonly="true"/>
<field name="_characterSet">""</field>
<property name="characterSet"
onget="return this._characterSet">
<setter><![CDATA[
this.messageManager.sendAsyncMessage("UpdateCharacterSet", {value: val});
this._characterSet = val;
]]></setter>
</property>
<field name="_mayEnableCharacterEncodingMenu">null</field>
<property name="mayEnableCharacterEncodingMenu"
onget="return this._mayEnableCharacterEncodingMenu;"
readonly="true"/>
<field name="_contentWindow">null</field>
<property name="contentWindow"
onget="return null"
readonly="true"/>
<property name="contentWindowAsCPOW"
onget="return this._contentWindow"
readonly="true"/>
<property name="contentDocument"
onget="return null"
readonly="true"/>
<field name="_contentPrincipal">null</field>
<property name="contentPrincipal"
onget="return this._contentPrincipal"
readonly="true"/>
<field name="_contentRequestContextID">null</field>
<property name="contentRequestContextID"
onget="return this._contentRequestContextID"
readonly="true"/>
<field name="_contentDocument">null</field>
<property name="contentDocumentAsCPOW"
onget="return this._contentDocument"
readonly="true"/>
<field name="_imageDocument">null</field>
<property name="imageDocument"
onget="return this._imageDocument"
readonly="true"/>
<field name="_fullZoom">1</field>
<property name="fullZoom">
<getter><![CDATA[
return this._fullZoom;
]]></getter>
<setter><![CDATA[
let changed = val.toFixed(2) != this._fullZoom.toFixed(2);
this._fullZoom = val;
try {
this.messageManager.sendAsyncMessage("FullZoom", {value: val});
} catch (ex) {}
if (changed) {
let event = new Event("FullZoomChange", {bubbles: true});
this.dispatchEvent(event);
}
]]></setter>
</property>
<field name="_textZoom">1</field>
<property name="textZoom">
<getter><![CDATA[
return this._textZoom;
]]></getter>
<setter><![CDATA[
let changed = val.toFixed(2) != this._textZoom.toFixed(2);
this._textZoom = val;
try {
this.messageManager.sendAsyncMessage("TextZoom", {value: val});
} catch (ex) {}
if (changed) {
let event = new Event("TextZoomChange", {bubbles: true});
this.dispatchEvent(event);
}
]]></setter>
</property>
<field name="_isSyntheticDocument">false</field>
<property name="isSyntheticDocument">
<getter><![CDATA[
return this._isSyntheticDocument;
]]></getter>
</property>
<property name="hasContentOpener">
<getter><![CDATA[
return this.frameLoader.tabParent.hasContentOpener;
]]></getter>
</property>
<field name="_outerWindowID">null</field>
<property name="outerWindowID"
onget="return this._outerWindowID"
readonly="true"/>
<field name="_innerWindowID">null</field>
<property name="innerWindowID">
<getter><![CDATA[
return this._innerWindowID;
]]></getter>
</property>
<property name="docShellIsActive">
<getter>
<![CDATA[
return this.frameLoader.tabParent.docShellIsActive;
]]>
</getter>
<setter>
<![CDATA[
this.frameLoader.tabParent.docShellIsActive = val;
return val;
]]>
</setter>
</property>
<method name="preserveLayers">
<parameter name="preserve"/>
<body><![CDATA[
let {frameLoader} = this;
if (frameLoader.tabParent) {
frameLoader.tabParent.preserveLayers(preserve);
}
]]></body>
</method>
<field name="_manifestURI"/>
<property name="manifestURI"
onget="return this._manifestURI"
readonly="true"/>
<field name="mDestroyed">false</field>
<field name="_permitUnloadId">0</field>
<method name="getInPermitUnload">
<parameter name="aCallback"/>
<body>
<![CDATA[
let id = this._permitUnloadId++;
let mm = this.messageManager;
mm.sendAsyncMessage("InPermitUnload", {id});
mm.addMessageListener("InPermitUnload", function listener(msg) {
if (msg.data.id != id) {
return;
}
aCallback(msg.data.inPermitUnload);
});
]]>
</body>
</method>
<method name="permitUnload">
<parameter name="aPermitUnloadFlags"/>
<body>
<![CDATA[
let {tabParent} = this.frameLoader;
if (!tabParent.hasBeforeUnload) {
return { permitUnload: true, timedOut: false };
}
const kTimeout = 1000;
let finished = false;
let responded = false;
let permitUnload;
let id = this._permitUnloadId++;
let mm = this.messageManager;
let Services = Components.utils.import("resource://gre/modules/Services.jsm", {}).Services;
let msgListener = msg => {
if (msg.data.id != id) {
return;
}
if (msg.data.kind == "start") {
responded = true;
return;
}
done(msg.data.permitUnload);
};
let observer = subject => {
if (subject == mm) {
done(true);
}
};
function done(result) {
finished = true;
permitUnload = result;
mm.removeMessageListener("PermitUnload", msgListener);
Services.obs.removeObserver(observer, "message-manager-close");
}
mm.sendAsyncMessage("PermitUnload", {id, aPermitUnloadFlags});
mm.addMessageListener("PermitUnload", msgListener);
Services.obs.addObserver(observer, "message-manager-close");
let timedOut = false;
function timeout() {
if (!responded) {
timedOut = true;
}
// Dispatch something to ensure that the main thread wakes up.
Services.tm.dispatchToMainThread(function() {});
}
let timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
timer.initWithCallback(timeout, kTimeout, timer.TYPE_ONE_SHOT);
while (!finished && !timedOut) {
Services.tm.currentThread.processNextEvent(true);
}
return {permitUnload, timedOut};
]]>
</body>
</method>
<constructor>
<![CDATA[
/*
* Don't try to send messages from this function. The message manager for
* the <browser> element may not be initialized yet.
*/
this._remoteWebNavigation = Components.classes["@mozilla.org/remote-web-navigation;1"]
.createInstance(Components.interfaces.nsIWebNavigation);
this._remoteWebNavigationImpl = this._remoteWebNavigation.wrappedJSObject;
this._remoteWebNavigationImpl.swapBrowser(this);
// Initialize contentPrincipal to the about:blank principal for this loadcontext
let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
let aboutBlank = Services.io.newURI("about:blank");
let ssm = Services.scriptSecurityManager;
this._contentPrincipal = ssm.getLoadContextCodebasePrincipal(aboutBlank, this.loadContext);
this.messageManager.addMessageListener("Browser:Init", this);
this.messageManager.addMessageListener("DOMTitleChanged", this);
this.messageManager.addMessageListener("ImageDocumentLoaded", this);
this.messageManager.addMessageListener("FullZoomChange", this);
this.messageManager.addMessageListener("TextZoomChange", this);
this.messageManager.addMessageListener("ZoomChangeUsingMouseWheel", this);
this.messageManager.addMessageListener("DOMFullscreen:RequestExit", this);
this.messageManager.addMessageListener("DOMFullscreen:RequestRollback", this);
this.messageManager.addMessageListener("MozApplicationManifest", this);
this.messageManager.loadFrameScript("chrome://global/content/browser-child.js", true);
if (this.hasAttribute("selectmenulist")) {
this.messageManager.addMessageListener("Forms:ShowDropDown", this);
this.messageManager.addMessageListener("Forms:HideDropDown", this);
}
if (!this.hasAttribute("disablehistory")) {
Services.obs.addObserver(this, "browser:purge-session-history", true);
}
let rc_js = "resource://gre/modules/RemoteController.js";
let scope = {};
Services.scriptloader.loadSubScript(rc_js, scope);
let RemoteController = scope.RemoteController;
this._controller = new RemoteController(this);
this.controllers.appendController(this._controller);
]]>
</constructor>
<destructor>
<![CDATA[
this.destroy();
]]>
</destructor>
<!-- This is necessary because the destructor doesn't always get called when
we are removed from a tabbrowser. This will be explicitly called by tabbrowser.
Note: This overrides the destroy() method from browser.xml. -->
<method name="destroy">
<body><![CDATA[
// Make sure that any open select is closed.
if (this._selectParentHelper) {
let menulist = document.getElementById(this.getAttribute("selectmenulist"));
this._selectParentHelper.hide(menulist, this);
}
if (this.mDestroyed)
return;
this.mDestroyed = true;
try {
this.controllers.removeController(this._controller);
} catch (ex) {
// This can fail when this browser element is not attached to a
// BrowserDOMWindow.
}
if (!this.hasAttribute("disablehistory")) {
let Services = Components.utils.import("resource://gre/modules/Services.jsm", {}).Services;
try {
Services.obs.removeObserver(this, "browser:purge-session-history");
} catch (ex) {
// It's not clear why this sometimes throws an exception.
}
}
]]></body>
</method>
<method name="receiveMessage">
<parameter name="aMessage"/>
<body><![CDATA[
let data = aMessage.data;
switch (aMessage.name) {
case "Browser:Init":
this._outerWindowID = data.outerWindowID;
break;
case "DOMTitleChanged":
this._contentTitle = data.title;
break;
case "ImageDocumentLoaded":
this._imageDocument = {
width: data.width,
height: data.height
};
break;
case "Forms:ShowDropDown": {
if (!this._selectParentHelper) {
this._selectParentHelper =
Cu.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
}
let menulist = document.getElementById(this.getAttribute("selectmenulist"));
menulist.menupopup.style.direction = data.direction;
let zoom = Services.prefs.getBoolPref("browser.zoom.full") ||
this.isSyntheticDocument ? this._fullZoom : this._textZoom;
this._selectParentHelper.populate(menulist, data.options, data.selectedIndex,
zoom, data.uaBackgroundColor, data.uaColor,
data.uaSelectBackgroundColor, data.uaSelectColor,
data.selectBackgroundColor, data.selectColor, data.selectTextShadow);
this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
break;
}
case "FullZoomChange": {
this._fullZoom = data.value;
let event = document.createEvent("Events");
event.initEvent("FullZoomChange", true, false);
this.dispatchEvent(event);
break;
}
case "TextZoomChange": {
this._textZoom = data.value;
let event = document.createEvent("Events");
event.initEvent("TextZoomChange", true, false);
this.dispatchEvent(event);
break;
}
case "ZoomChangeUsingMouseWheel": {
let event = document.createEvent("Events");
event.initEvent("ZoomChangeUsingMouseWheel", true, false);
this.dispatchEvent(event);
break;
}
case "DOMFullscreen:RequestExit": {
let windowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
windowUtils.exitFullscreen();
break;
}
case "DOMFullscreen:RequestRollback": {
let windowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
windowUtils.remoteFrameFullscreenReverted();
break;
}
case "MozApplicationManifest":
this._manifestURI = aMessage.data.manifest;
break;
default:
// Delegate to browser.xml.
return this._receiveMessage(aMessage);
}
return undefined;
]]></body>
</method>
<method name="enableDisableCommands">
<parameter name="aAction"/>
<parameter name="aEnabledLength"/>
<parameter name="aEnabledCommands"/>
<parameter name="aDisabledLength"/>
<parameter name="aDisabledCommands"/>
<body>
if (this._controller) {
this._controller.enableDisableCommands(aAction,
aEnabledLength, aEnabledCommands,
aDisabledLength, aDisabledCommands);
}
</body>
</method>
<method name="purgeSessionHistory">
<body>
<![CDATA[
try {
this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
} catch (ex) {
// This can throw if the browser has started to go away.
if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED) {
throw ex;
}
}
this._remoteWebNavigationImpl.canGoBack = false;
this._remoteWebNavigationImpl.canGoForward = false;
]]>
</body>
</method>
<method name="createAboutBlankContentViewer">
<parameter name="aPrincipal"/>
<body>
<![CDATA[
// Ensure that the content process has the permissions which are
// needed to create a document with the given principal.
let permissionPrincipal =
BrowserUtils.principalWithMatchingOA(aPrincipal, this.contentPrincipal);
this.frameLoader.tabParent.transmitPermissionsForPrincipal(permissionPrincipal);
// Create the about blank content viewer in the content process
this.messageManager.sendAsyncMessage("Browser:CreateAboutBlank", aPrincipal);
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="dragstart">
<![CDATA[
// If we're a remote browser dealing with a dragstart, stop it
// from propagating up, since our content process should be dealing
// with the mouse movement.
event.stopPropagation();
]]>
</handler>
</handlers>
</binding>
</bindings>