<?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>

      <!-- increases or decreases the browser's network priority -->
      <method name="adjustPriority">
        <parameter name="adjustment"/>
        <body><![CDATA[
          this.messageManager.sendAsyncMessage("NetworkPrioritizer:AdjustPriority",
                                               {adjustment});
        ]]></body>
      </method>

      <!-- sets the browser's network priority to a discrete value -->
      <method name="setPriority">
        <parameter name="priority"/>
        <body><![CDATA[
          this.messageManager.sendAsyncMessage("NetworkPrioritizer:SetPriority",
                                               {priority});
        ]]></body>
      </method>

      <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"/>

      <property name="contentDocumentAsCPOW"
                onget="return this.contentWindowAsCPOW ? this.contentWindowAsCPOW.document : null"
                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;
          this.messageManager.sendAsyncMessage("FullZoom", {value: val});

          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;
          this.messageManager.sendAsyncMessage("TextZoom", {value: val});

          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[
          let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
          return 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[
            let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
            return frameLoader.tabParent.docShellIsActive;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
            frameLoader.tabParent.docShellIsActive = val;
            return val;
          ]]>
        </setter>
      </property>

      <method name="preserveLayers">
        <parameter name="preserve"/>
        <body><![CDATA[
          let {frameLoader} = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
          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">
        <body>
        <![CDATA[
          const kTimeout = 5000;

          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});
          mm.addMessageListener("PermitUnload", msgListener);
          Services.obs.addObserver(observer, "message-manager-close", false);

          let timedOut = false;
          function timeout() {
            if (!responded) {
              timedOut = true;
            }

            // Dispatch something to ensure that the main thread wakes up.
            Services.tm.mainThread.dispatch(function() {}, Components.interfaces.nsIThread.DISPATCH_NORMAL);
          }

          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", null, null);
          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);
            this.messageManager.loadFrameScript("chrome://global/content/select-child.js", true);
          }

          if (!this.hasAttribute("disablehistory")) {
            Services.obs.addObserver(this, "browser:purge-session-history", true);
          }

          let jsm = "resource://gre/modules/RemoteController.jsm";
          let RemoteController = Components.utils.import(jsm, {}).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);
              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 "Forms:HideDropDown": {
              if (this._selectParentHelper) {
                let menulist = document.getElementById(this.getAttribute("selectmenulist"));
                this._selectParentHelper.hide(menulist, this);
              }
              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[
            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>
