Bug 1441935 - Copy browser.xml and import the generated MozBrowser Custom Element r=mconley
authorBrian Grinstead <bgrinstead@mozilla.com>
Thu, 10 Jan 2019 01:45:41 +0000
changeset 510299 4e943c6de64e19c107902ca41e5d18a6ec9d1395
parent 510298 740f1995b1281671ca4fe49ad97d834a19d29016
child 510300 bd38d0bde8a3654bd1d727ebb5f301535b6da273
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1441935
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1441935 - Copy browser.xml and import the generated MozBrowser Custom Element r=mconley Differential Revision: https://phabricator.services.mozilla.com/D14910
toolkit/content/widgets/browser-custom-element.js
copy from toolkit/content/widgets/browser.xml
copy to toolkit/content/widgets/browser-custom-element.js
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser-custom-element.js
@@ -1,2088 +1,1826 @@
-<?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/. */
+
+  /* eslint-disable */
+
+"use strict";
+
+// This is loaded into all XUL windows. Wrap in a block to prevent
+// leaking to window scope.
+{
+
+class MozBrowser extends MozXULElement {
+  constructor() {
+    super();
 
-<!-- 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.addEventListener("keypress", (event) => {
+      if (event.keyCode != KeyEvent.DOM_VK_F7) {
+        return;
+      }
+
+      if (event.defaultPrevented || !event.isTrusted)
+        return;
 
-<bindings id="browserBindings"
-          xmlns="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+      const kPrefShortcutEnabled = "accessibility.browsewithcaret_shortcut.enabled";
+      const kPrefWarnOnEnable = "accessibility.warn_on_browsewithcaret";
+      const kPrefCaretBrowsingOn = "accessibility.browsewithcaret";
+
+      var isEnabled = this.mPrefs.getBoolPref(kPrefShortcutEnabled);
+      if (!isEnabled)
+        return;
 
-  <binding id="browser">
-    <content clickthrough="never">
-      <children/>
-    </content>
-    <implementation type="application/javascript" implements="nsIObserver, nsIBrowser">
-      <property name="autoscrollEnabled">
-        <getter>
-          <![CDATA[
-            if (this.getAttribute("autoscroll") == "false")
-              return false;
+      // Toggle browse with caret mode
+      var browseWithCaretOn = this.mPrefs.getBoolPref(kPrefCaretBrowsingOn, false);
+      var warn = this.mPrefs.getBoolPref(kPrefWarnOnEnable, true);
+      if (warn && !browseWithCaretOn) {
+        var checkValue = { value: false };
+        var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"]
+          .getService(Ci.nsIPromptService);
 
-            return this.mPrefs.getBoolPref("general.autoScroll", true);
-          ]]>
-        </getter>
-      </property>
+        var buttonPressed = promptService.confirmEx(window,
+          this.mStrBundle.GetStringFromName("browsewithcaret.checkWindowTitle"),
+          this.mStrBundle.GetStringFromName("browsewithcaret.checkLabel"),
+          // Make "No" the default:
+          promptService.STD_YES_NO_BUTTONS | promptService.BUTTON_POS_1_DEFAULT,
+          null, null, null, this.mStrBundle.GetStringFromName("browsewithcaret.checkMsg"),
+          checkValue);
+        if (buttonPressed != 0) {
+          if (checkValue.value) {
+            try {
+              this.mPrefs.setBoolPref(kPrefShortcutEnabled, false);
+            } catch (ex) {}
+          }
+          return;
+        }
+        if (checkValue.value) {
+          try {
+            this.mPrefs.setBoolPref(kPrefWarnOnEnable, false);
+          } catch (ex) {}
+        }
+      }
+
+      // Toggle the pref
+      try {
+        this.mPrefs.setBoolPref(kPrefCaretBrowsingOn, !browseWithCaretOn);
+      } catch (ex) {}
+    }, { mozSystemGroup: true });
+
+    this.addEventListener("dragover", (event) => {
+      if (!this.droppedLinkHandler || event.defaultPrevented)
+        return;
 
-      <property name="canGoBack"
-                onget="return this.webNavigation.canGoBack;"
-                readonly="true"/>
+      // For drags that appear to be internal text (for example, tab drags),
+      // set the dropEffect to 'none'. This prevents the drop even if some
+      // other listener cancelled the event.
+      var types = event.dataTransfer.types;
+      if (types.includes("text/x-moz-text-internal") && !types.includes("text/plain")) {
+        event.dataTransfer.dropEffect = "none";
+        event.stopPropagation();
+        event.preventDefault();
+      }
+
+      // No need to handle "dragover" in e10s, since nsDocShellTreeOwner.cpp in the child process
+      // handles that case using "@mozilla.org/content/dropped-link-handler;1" service.
+      if (this.isRemoteBrowser)
+        return;
 
-      <property name="canGoForward"
-                onget="return this.webNavigation.canGoForward;"
-                readonly="true"/>
+      let linkHandler = Cc["@mozilla.org/content/dropped-link-handler;1"].
+      getService(Ci.nsIDroppedLinkHandler);
+      if (linkHandler.canDropLink(event, false))
+        event.preventDefault();
+    }, { mozSystemGroup: true });
+
+    this.addEventListener("drop", (event) => {
+      // No need to handle "drop" in e10s, since nsDocShellTreeOwner.cpp in the child process
+      // handles that case using "@mozilla.org/content/dropped-link-handler;1" service.
+      if (!this.droppedLinkHandler || event.defaultPrevented || this.isRemoteBrowser)
+        return;
+
+      let linkHandler = Cc["@mozilla.org/content/dropped-link-handler;1"].
+      getService(Ci.nsIDroppedLinkHandler);
+      try {
+        // Pass true to prevent the dropping of javascript:/data: URIs
+        var links = linkHandler.dropLinks(event, true);
+      } catch (ex) {
+        return;
+      }
 
-      <method name="_wrapURIChangeCall">
-        <parameter name="fn"/>
-        <body>
-          <![CDATA[
-            if (!this.isRemoteBrowser) {
-              this.inLoadURI = true;
-              try {
-                fn();
-              } finally {
-                this.inLoadURI = false;
-              }
-            } else {
-              fn();
-            }
-          ]]>
-        </body>
-      </method>
+      if (links.length) {
+        let triggeringPrincipal = linkHandler.getTriggeringPrincipal(event);
+        this.droppedLinkHandler(event, links, triggeringPrincipal);
+      }
+    }, { mozSystemGroup: true });
+
+    this.addEventListener("dragstart", (event) => {
+      // 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.
+      if (this.isRemoteBrowser) {
+        event.stopPropagation();
+      }
+    });
+
+  }
 
+  connectedCallback() {
+    if (this.delayConnectedCallback()) {
+      return;
+    }
+    this.textContent = "";
+    this.appendChild(MozXULElement.parseXULToFragment(`
+      <children></children>
+    `));
+
+    this._documentURI = null;
+
+    this._documentContentType = null;
+
+    /**
+     * Weak reference to an optional frame loader that can be used to influence
+     * process selection for this browser.
+     * See nsIBrowser.sameProcessAsFrameLoader.
+     */
+    this._sameProcessAsFrameLoader = null;
+
+    this._loadContext = null;
+
+    this._imageDocument = null;
 
-      <method name="goBack">
-        <body>
-          <![CDATA[
-            var webNavigation = this.webNavigation;
-            if (webNavigation.canGoBack)
-              this._wrapURIChangeCall(() => webNavigation.goBack());
-          ]]>
-        </body>
-      </method>
+    this._webBrowserFind = null;
+
+    this._finder = null;
+
+    this._remoteFinder = null;
+
+    this._fastFind = null;
+
+    this._outerWindowID = null;
+
+    this._innerWindowID = null;
+
+    this._browsingContextId = null;
+
+    this._lastSearchString = null;
+
+    this._controller = null;
+
+    this._selectParentHelper = null;
+
+    this._remoteWebNavigation = null;
+
+    this._remoteWebProgress = null;
+
+    this._contentTitle = "";
+
+    this._characterSet = "";
+
+    this._mayEnableCharacterEncodingMenu = null;
+
+    this._contentPrincipal = null;
+
+    this._contentRequestContextID = null;
+
+    this._fullZoom = 1;
 
-      <method name="goForward">
-        <body>
-          <![CDATA[
-            var webNavigation = this.webNavigation;
-            if (webNavigation.canGoForward)
-              this._wrapURIChangeCall(() => webNavigation.goForward());
-          ]]>
-        </body>
-      </method>
+    this._textZoom = 1;
+
+    this._isSyntheticDocument = false;
+
+    this.mPrefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+    this._mStrBundle = null;
+
+    this.blockedPopups = null;
+
+    this._audioMuted = false;
+
+    this._hasAnyPlayingMediaBeenBlocked = false;
 
-      <method name="reload">
-        <body>
-          <![CDATA[
-            const nsIWebNavigation = Ci.nsIWebNavigation;
-            const flags = nsIWebNavigation.LOAD_FLAGS_NONE;
-            this.reloadWithFlags(flags);
-          ]]>
-        </body>
-      </method>
+    /**
+     * Only send the message "Browser:UnselectedTabHover" when someone requests
+     * for the message, which can reduce non-necessary communication.
+     */
+    this._shouldSendUnselectedTabHover = false;
+
+    this._unselectedTabHoverMessageListenerCount = 0;
+
+    this._securityUI = null;
+
+    this.urlbarChangeTracker = {
+      _startedLoadSinceLastUserTyping: false,
+
+      startedLoad() {
+        this._startedLoadSinceLastUserTyping = true;
+      },
+      finishedLoad() {
+        this._startedLoadSinceLastUserTyping = false;
+      },
+      userTyped() {
+        this._startedLoadSinceLastUserTyping = false;
+      },
+    };
 
-      <method name="reloadWithFlags">
-        <parameter name="aFlags"/>
-        <body>
-          <![CDATA[
-            this.webNavigation.reload(aFlags);
-          ]]>
-        </body>
-      </method>
+    this._userTypedValue = null;
+
+    this.droppedLinkHandler = null;
+
+    this.mIconURL = null;
+
+    /**
+     * This is managed by the tabbrowser
+     */
+    this.lastURI = null;
+
+    this.mDestroyed = false;
+
+    this._AUTOSCROLL_SNAP = 10;
+
+    this._scrolling = false;
+
+    this._startX = null;
+
+    this._startY = null;
+
+    this._autoScrollPopup = null;
+
+    this._autoScrollNeedsCleanup = false;
+
+    /**
+     * These IDs identify the scroll frame being autoscrolled.
+     */
+    this._autoScrollScrollId = null;
+
+    this._autoScrollPresShellId = null;
+
+    this._permitUnloadId = 0;
+
+    this.construct();
 
-      <method name="stop">
-        <body>
-          <![CDATA[
-            const nsIWebNavigation = Ci.nsIWebNavigation;
-            const flags = nsIWebNavigation.STOP_ALL;
-            this.webNavigation.stop(flags);
-          ]]>
-        </body>
-      </method>
+  }
+
+  get autoscrollEnabled() {
+    if (this.getAttribute("autoscroll") == "false")
+      return false;
+
+    return this.mPrefs.getBoolPref("general.autoScroll", true);
+  }
+
+  get canGoBack() {
+    return this.webNavigation.canGoBack;
+  }
+
+  get canGoForward() {
+    return this.webNavigation.canGoForward;
+  }
 
-      <!-- throws exception for unknown schemes -->
-      <method name="loadURI">
-        <parameter name="aURI"/>
-        <parameter name="aParams"/>
-        <body>
-          <![CDATA[
-            if (!aURI) {
-              aURI = "about:blank";
-            }
+  get currentURI() {
+    if (this.webNavigation) {
+      return this.webNavigation.currentURI;
+    }
+    return null;
+  }
+
+  get documentURI() {
+    return this.isRemoteBrowser ? this._documentURI : this.contentDocument.documentURIObject;
+  }
 
-            let {
-              flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
-              referrerURI,
-              referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_UNSET,
-              triggeringPrincipal,
-              postData,
-            } = aParams || {};
+  get documentContentType() {
+    if (this.isRemoteBrowser) {
+      return this._documentContentType;
+    }
+    return this.contentDocument ? this.contentDocument.contentType : null;
+  }
+
+  set sameProcessAsFrameLoader(val) {
+    this._sameProcessAsFrameLoader = Cu.getWeakReference(val);
+  }
 
-            this._wrapURIChangeCall(() =>
-              this.webNavigation.loadURIWithOptions(
-                  aURI, flags, referrerURI, referrerPolicy,
-                  postData, null, null, triggeringPrincipal));
-          ]]>
-        </body>
-      </method>
+  get sameProcessAsFrameLoader() {
+    return this._sameProcessAsFrameLoader && this._sameProcessAsFrameLoader.get();
+  }
+
+  get loadContext() {
+    if (this._loadContext)
+      return this._loadContext;
+
+    let { frameLoader } = this;
+    if (!frameLoader)
+      return null;
+    this._loadContext = frameLoader.loadContext;
+    return this._loadContext;
+  }
+
+  get autoCompletePopup() {
+    return document.getElementById(this.getAttribute('autocompletepopup'))
+  }
+
+  get dateTimePicker() {
+    return document.getElementById(this.getAttribute('datetimepicker'))
+  }
+
+  set docShellIsActive(val) {
+    if (this.isRemoteBrowser) {
+      this.frameLoader.tabParent.docShellIsActive = val;
+      return val;
+    }
+    if (this.docShell)
+      return this.docShell.isActive = val;
+    return false;
+  }
+
+  get docShellIsActive() {
+    if (this.isRemoteBrowser) {
+      return this.frameLoader.tabParent.docShellIsActive;
+    }
+    return this.docShell && this.docShell.isActive;
+  }
+
+  set renderLayers(val) {
+    if (this.isRemoteBrowser) {
+      let { frameLoader } = this;
+      if (frameLoader && frameLoader.tabParent) {
+        return frameLoader.tabParent.renderLayers = val;
+      }
+      return false;
+    }
+    return this.docShellIsActive = val;
+  }
+
+  get renderLayers() {
+    if (this.isRemoteBrowser) {
+      let { frameLoader } = this;
+      if (frameLoader && frameLoader.tabParent) {
+        return frameLoader.tabParent.renderLayers;
+      }
+      return false;
+    }
+    return this.docShellIsActive;
+  }
 
-      <method name="gotoIndex">
-        <parameter name="aIndex"/>
-        <body>
-          <![CDATA[
-            this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(aIndex));
-          ]]>
-        </body>
-      </method>
+  get hasLayers() {
+    if (this.isRemoteBrowser) {
+      let { frameLoader } = this;
+      if (frameLoader.tabParent) {
+        return frameLoader.tabParent.hasLayers;
+      }
+      return false;
+    }
+
+    return this.docShellIsActive;
+  }
+
+  get imageDocument() {
+    if (this.isRemoteBrowser) {
+      return this._imageDocument;
+    }
+    var document = this.contentDocument;
+    if (!document || !(document instanceof Ci.nsIImageDocument))
+      return null;
+
+    try {
+      return { width: document.imageRequest.image.width, height: document.imageRequest.image.height };
+    } catch (e) {}
+    return null;
+  }
+
+  get isRemoteBrowser() {
+    return (this.getAttribute('remote') == 'true');
+  }
 
-      <property name="currentURI" readonly="true">
-       <getter><![CDATA[
-          if (this.webNavigation) {
-            return this.webNavigation.currentURI;
-          }
+  get remoteType() {
+    if (!this.isRemoteBrowser) {
+      return null;
+    }
+
+    let remoteType = this.getAttribute("remoteType");
+    if (remoteType) {
+      return remoteType;
+    }
+
+    let E10SUtils = ChromeUtils.import("resource://gre/modules/E10SUtils.jsm", {}).E10SUtils;
+    return E10SUtils.DEFAULT_REMOTE_TYPE;
+  }
+
+  get messageManager() {
+    if (this.frameLoader) {
+      return this.frameLoader.messageManager;
+    }
+    return null;
+  }
+
+  get webBrowserFind() {
+    if (!this._webBrowserFind)
+      this._webBrowserFind = this.docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebBrowserFind);
+    return this._webBrowserFind;
+  }
+
+  get finder() {
+    if (this.isRemoteBrowser) {
+      if (!this._remoteFinder) {
+        // Don't attempt to create the remote finder if the
+        // messageManager has already gone away
+        if (!this.messageManager)
           return null;
-       ]]>
-       </getter>
-      </property>
+
+        let jsm = "resource://gre/modules/FinderParent.jsm";
+        let { FinderParent } = ChromeUtils.import(jsm, {});
+        this._remoteFinder = new FinderParent(this);
+      }
+      return this._remoteFinder;
+    }
+    if (!this._finder) {
+      if (!this.docShell)
+        return null;
 
-      <!--
-        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[
-          if (this.isRemoteBrowser) {
-            this._remoteWebProgressManager.setCurrentURI(aURI);
-          } else {
-            this.docShell.setCurrentURI(aURI);
-          }
-        ]]></body>
-      </method>
+      let Finder = ChromeUtils.import("resource://gre/modules/Finder.jsm", {}).Finder;
+      this._finder = new Finder(this.docShell);
+    }
+    return this._finder;
+  }
 
-      <field name="_documentURI">null</field>
-      <property name="documentURI"
-                onget="return this.isRemoteBrowser ? this._documentURI : this.contentDocument.documentURIObject;"
-                readonly="true"/>
-
+  get fastFind() {
+    if (!this._fastFind) {
+      if (!("@mozilla.org/typeaheadfind;1" in Cc))
+        return null;
 
-      <field name="_documentContentType">null</field>
-      <property name="documentContentType"
-                readonly="true">
-        <getter><![CDATA[
-          if (this.isRemoteBrowser) {
-            return this._documentContentType;
-          }
-          return this.contentDocument ? this.contentDocument.contentType : null;
-        ]]></getter>
-      </property>
+      var tabBrowser = this.getTabBrowser();
+      if (tabBrowser && "fastFind" in tabBrowser)
+        return this._fastFind = tabBrowser.fastFind;
+
+      if (!this.docShell)
+        return null;
+
+      this._fastFind = Cc["@mozilla.org/typeaheadfind;1"]
+        .createInstance(Ci.nsITypeAheadFind);
+      this._fastFind.init(this.docShell);
+    }
+    return this._fastFind;
+  }
+
+  get outerWindowID() {
+    if (this.isRemoteBrowser) {
+      return this._outerWindowID;
+    }
+    return this.docShell.outerWindowID;
+  }
+
+  get innerWindowID() {
+    if (this.isRemoteBrowser) {
+      return this._innerWindowID;
+    }
+    try {
+      return this.contentWindow.windowUtils.currentInnerWindowID;
+    } catch (e) {
+      if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
+        throw e;
+      }
+      return null;
+    }
+  }
 
-      <!--
-        Weak reference to an optional frame loader that can be used to influence
-        process selection for this browser.
-        See nsIBrowser.sameProcessAsFrameLoader.
-      -->
-      <field name="_sameProcessAsFrameLoader">null</field>
-      <property name="sameProcessAsFrameLoader">
-        <getter><![CDATA[
-          return this._sameProcessAsFrameLoader && this._sameProcessAsFrameLoader.get();
-        ]]></getter>
-        <setter><![CDATA[
-          this._sameProcessAsFrameLoader = Cu.getWeakReference(val);
-        ]]></setter>
-      </property>
+  get browsingContext() {
+    if (!this.isRemoteBrowser) {
+      return this.docShell.browsingContext;
+    }
+
+    return ChromeUtils.getBrowsingContext(this._browsingContextId);
+  }
+  /**
+   * Note that this overrides webNavigation on XULFrameElement, and duplicates the return value for the non-remote case
+   */
+  get webNavigation() {
+    return this.isRemoteBrowser ? this._remoteWebNavigation : this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);
+  }
+
+  get webProgress() {
+    return this.isRemoteBrowser ? this._remoteWebProgress : this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);
+  }
 
-      <field name="_loadContext">null</field>
+  get sessionHistory() {
+    return this.webNavigation.sessionHistory;
+  }
+
+  get markupDocumentViewer() {
+    return this.docShell.contentViewer;
+  }
 
-      <property name="loadContext" readonly="true">
-        <getter><![CDATA[
-          if (this._loadContext)
-            return this._loadContext;
+  get contentTitle() {
+    return this.isRemoteBrowser ? this._contentTitle : this.contentDocument.title;
+  }
+
+  set characterSet(val) {
+    if (this.isRemoteBrowser) {
+      this.messageManager.sendAsyncMessage("UpdateCharacterSet", { value: val });
+      this._characterSet = val;
+    } else {
+      this.docShell.charset = val;
+      this.docShell.gatherCharsetMenuTelemetry();
+    }
+  }
 
-          let {frameLoader} = this;
-          if (!frameLoader)
-            return null;
-          this._loadContext = frameLoader.loadContext;
-          return this._loadContext;
-        ]]></getter>
-      </property>
+  get characterSet() {
+    return this.isRemoteBrowser ? this._characterSet : this.docShell.charset;
+  }
+
+  get mayEnableCharacterEncodingMenu() {
+    return this.isRemoteBrowser ? this._mayEnableCharacterEncodingMenu : this.docShell.mayEnableCharacterEncodingMenu;
+  }
+
+  get contentPrincipal() {
+    return this.isRemoteBrowser ? this._contentPrincipal : this.contentDocument.nodePrincipal;
+  }
 
-      <property name="autoCompletePopup"
-                onget="return document.getElementById(this.getAttribute('autocompletepopup'))"
-                readonly="true"/>
+  get contentRequestContextID() {
+    if (this.isRemoteBrowser) {
+      return this._contentRequestContextID;
+    }
+    try {
+      return this.contentDocument.documentLoadGroup
+        .requestContextID;
+    } catch (e) {
+      return null;
+    }
+  }
 
-      <property name="dateTimePicker"
-                onget="return document.getElementById(this.getAttribute('datetimepicker'))"
-                readonly="true"/>
+  set showWindowResizer(val) {
+    if (val) this.setAttribute('showresizer', 'true');
+    else this.removeAttribute('showresizer');
+    return val;
+  }
+
+  get showWindowResizer() {
+    return this.getAttribute('showresizer') == 'true';
+  }
+
+  set fullZoom(val) {
+    if (this.isRemoteBrowser) {
+      let changed = val.toFixed(2) != this._fullZoom.toFixed(2);
 
-      <property name="docShellIsActive">
-        <getter>
-          <![CDATA[
-            if (this.isRemoteBrowser) {
-              return this.frameLoader.tabParent.docShellIsActive;
-            }
-            return this.docShell && this.docShell.isActive;
-          ]]>
-        </getter>
-        <setter>
-          <![CDATA[
-            if (this.isRemoteBrowser) {
-              this.frameLoader.tabParent.docShellIsActive = val;
-              return val;
-            }
-            if (this.docShell)
-              return this.docShell.isActive = val;
-            return false;
-          ]]>
-        </setter>
-      </property>
+      if (changed) {
+        this._fullZoom = val;
+        try {
+          this.messageManager.sendAsyncMessage("FullZoom", { value: val });
+        } catch (ex) {}
+
+        let event = new Event("FullZoomChange", { bubbles: true });
+        this.dispatchEvent(event);
+      }
+    } else {
+      this.markupDocumentViewer.fullZoom = val;
+    }
+  }
+
+  get fullZoom() {
+    if (this.isRemoteBrowser) {
+      return this._fullZoom;
+    }
+    return this.markupDocumentViewer.fullZoom;
+  }
+
+  set textZoom(val) {
+    if (this.isRemoteBrowser) {
+      let changed = val.toFixed(2) != this._textZoom.toFixed(2);
+
+      if (changed) {
+        this._textZoom = val;
+        try {
+          this.messageManager.sendAsyncMessage("TextZoom", { value: val });
+        } catch (ex) {}
+
+        let event = new Event("TextZoomChange", { bubbles: true });
+        this.dispatchEvent(event);
+      }
+    } else {
+      this.markupDocumentViewer.textZoom = val;
+    }
+
+  }
 
-      <method name="preserveLayers">
-        <parameter name="preserve"/>
-        <body><![CDATA[
-          if (!this.isRemoteBrowser) {
-            return;
-          }
-          let {frameLoader} = this;
-          if (frameLoader.tabParent) {
-            frameLoader.tabParent.preserveLayers(preserve);
-          }
-        ]]></body>
-      </method>
+  get textZoom() {
+    if (this.isRemoteBrowser) {
+      return this._textZoom;
+    }
+    return this.markupDocumentViewer.textZoom;
+  }
+
+  get isSyntheticDocument() {
+    if (this.isRemoteBrowser) {
+      return this._isSyntheticDocument;
+    }
+    return this.contentDocument.mozSyntheticDocument;
+  }
+
+  get hasContentOpener() {
+    if (this.isRemoteBrowser) {
+      return this.frameLoader.tabParent.hasContentOpener;
+    }
+    return !!this.contentWindow.opener;
+  }
+
+  get mStrBundle() {
+    if (!this._mStrBundle) {
+      // need to create string bundle manually instead of using <xul:stringbundle/>
+      // see bug 63370 for details
+      this._mStrBundle = Cc["@mozilla.org/intl/stringbundle;1"]
+        .getService(Ci.nsIStringBundleService)
+        .createBundle("chrome://global/locale/browser.properties");
+    }
+    return this._mStrBundle;
+  }
 
-      <method name="deprioritize">
-        <body><![CDATA[
-          if (!this.isRemoteBrowser) {
-            return;
-          }
-          let {frameLoader} = this;
-          if (frameLoader.tabParent) {
-            frameLoader.tabParent.deprioritize();
-          }
-        ]]></body>
-      </method>
+  get audioMuted() {
+    return this._audioMuted;
+  }
+
+  get shouldHandleUnselectedTabHover() {
+    return this._shouldSendUnselectedTabHover;
+  }
+
+  get securityUI() {
+    if (this.isRemoteBrowser) {
+      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 = ChromeUtils.import(jsm, {}).RemoteSecurityUI;
+        this._securityUI = new RemoteSecurityUI();
+      }
+
+      // We want to double-wrap the JS implemented interface, so that QI and instanceof works.
+      var ptr = Cc["@mozilla.org/supports-interface-pointer;1"]
+        .createInstance(Ci.nsISupportsInterfacePointer);
+      ptr.data = this._securityUI;
+      return ptr.data.QueryInterface(Ci.nsISecureBrowserUI);
+    }
+
+    if (!this.docShell.securityUI) {
+      const SECUREBROWSERUI_CONTRACTID = "@mozilla.org/secure_browser_ui;1";
+      var securityUI = Cc[SECUREBROWSERUI_CONTRACTID]
+        .createInstance(Ci.nsISecureBrowserUI);
+      securityUI.init(this.docShell);
+    }
+
+    return this.docShell.securityUI;
+  }
 
-      <property name="renderLayers">
-        <getter>
-          <![CDATA[
-            if (this.isRemoteBrowser) {
-              let {frameLoader} = this;
-              if (frameLoader && frameLoader.tabParent) {
-                return frameLoader.tabParent.renderLayers;
-              }
-              return false;
-            }
-            return this.docShellIsActive;
-          ]]>
-        </getter>
-        <setter>
-          <![CDATA[
-            if (this.isRemoteBrowser) {
-              let {frameLoader} = this;
-              if (frameLoader && frameLoader.tabParent) {
-                return frameLoader.tabParent.renderLayers = val;
-              }
-              return false;
-            }
-            return this.docShellIsActive = val;
-          ]]>
-        </setter>
-      </property>
+  set userTypedValue(val) {
+    this.urlbarChangeTracker.userTyped();
+    this._userTypedValue = val;
+    return val;
+  }
+
+  get userTypedValue() {
+    return this._userTypedValue;
+  }
+
+  get dontPromptAndDontUnload() {
+    return 1;
+  }
+
+  get dontPromptAndUnload() {
+    return 2;
+  }
+
+  _wrapURIChangeCall(fn) {
+    if (!this.isRemoteBrowser) {
+      this.inLoadURI = true;
+      try {
+        fn();
+      } finally {
+        this.inLoadURI = false;
+      }
+    } else {
+      fn();
+    }
+  }
+
+  goBack() {
+    var webNavigation = this.webNavigation;
+    if (webNavigation.canGoBack)
+      this._wrapURIChangeCall(() => webNavigation.goBack());
+  }
+
+  goForward() {
+    var webNavigation = this.webNavigation;
+    if (webNavigation.canGoForward)
+      this._wrapURIChangeCall(() => webNavigation.goForward());
+  }
+
+  reload() {
+    const nsIWebNavigation = Ci.nsIWebNavigation;
+    const flags = nsIWebNavigation.LOAD_FLAGS_NONE;
+    this.reloadWithFlags(flags);
+  }
+
+  reloadWithFlags(aFlags) {
+    this.webNavigation.reload(aFlags);
+  }
+
+  stop() {
+    const nsIWebNavigation = Ci.nsIWebNavigation;
+    const flags = nsIWebNavigation.STOP_ALL;
+    this.webNavigation.stop(flags);
+  }
+
+  /**
+   * throws exception for unknown schemes
+   */
+  loadURI(aURI, aParams) {
+    if (!aURI) {
+      aURI = "about:blank";
+    }
+
+    let {
+      flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
+        referrerURI,
+        referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_UNSET,
+        triggeringPrincipal,
+        postData,
+    } = aParams || {};
+
+    this._wrapURIChangeCall(() =>
+      this.webNavigation.loadURIWithOptions(
+        aURI, flags, referrerURI, referrerPolicy,
+        postData, null, null, triggeringPrincipal));
+  }
 
-      <property name="hasLayers" readonly="true">
-        <getter>
-          <![CDATA[
-            if (this.isRemoteBrowser) {
-              let {frameLoader} = this;
-              if (frameLoader.tabParent) {
-                return frameLoader.tabParent.hasLayers;
-              }
-              return false;
-            }
+  gotoIndex(aIndex) {
+    this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(aIndex));
+  }
+
+  /**
+   * 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.
+   */
+  _setCurrentURI(aURI) {
+    if (this.isRemoteBrowser) {
+      this._remoteWebProgressManager.setCurrentURI(aURI);
+    } else {
+      this.docShell.setCurrentURI(aURI);
+    }
+  }
+
+  preserveLayers(preserve) {
+    if (!this.isRemoteBrowser) {
+      return;
+    }
+    let { frameLoader } = this;
+    if (frameLoader.tabParent) {
+      frameLoader.tabParent.preserveLayers(preserve);
+    }
+  }
 
-            return this.docShellIsActive;
-          ]]>
-        </getter>
-      </property>
+  deprioritize() {
+    if (!this.isRemoteBrowser) {
+      return;
+    }
+    let { frameLoader } = this;
+    if (frameLoader.tabParent) {
+      frameLoader.tabParent.deprioritize();
+    }
+  }
+
+  forceRepaint() {
+    if (!this.isRemoteBrowser) {
+      return;
+    }
+    let { frameLoader } = this;
+    if (frameLoader && frameLoader.tabParent) {
+      frameLoader.tabParent.forceRepaint();
+    }
+  }
 
-      <method name="forceRepaint">
-        <body>
-          <![CDATA[
-            if (!this.isRemoteBrowser) {
-              return;
-            }
-            let {frameLoader} = this;
-            if (frameLoader && frameLoader.tabParent) {
-              frameLoader.tabParent.forceRepaint();
-            }
-          ]]>
-        </body>
-      </method>
+  getTabBrowser() {
+    if (this.ownerGlobal.gBrowser &&
+      this.ownerGlobal.gBrowser.getTabForBrowser &&
+      this.ownerGlobal.gBrowser.getTabForBrowser(this)) {
+      return this.ownerGlobal.gBrowser;
+    }
+    return null;
+  }
+
+  addProgressListener(aListener, aNotifyMask) {
+    if (!aNotifyMask) {
+      aNotifyMask = Ci.nsIWebProgress.NOTIFY_ALL;
+    }
+    this.webProgress.addProgressListener(aListener, aNotifyMask);
+  }
+
+  removeProgressListener(aListener) {
+    this.webProgress.removeProgressListener(aListener);
+  }
 
-      <field name="_imageDocument">null</field>
-      <property name="imageDocument"
-                readonly="true">
-        <getter>
-          <![CDATA[
-            if (this.isRemoteBrowser) {
-              return this._imageDocument;
-            }
-            var document = this.contentDocument;
-            if (!document || !(document instanceof Ci.nsIImageDocument))
-              return null;
+  onPageHide(aEvent) {
+    if (!this.docShell || !this.fastFind)
+      return;
+    var tabBrowser = this.getTabBrowser();
+    if (!tabBrowser || !("fastFind" in tabBrowser) ||
+      tabBrowser.selectedBrowser == this)
+      this.fastFind.setDocShell(this.docShell);
+  }
+
+  updateBlockedPopups() {
+    let event = document.createEvent("Events");
+    event.initEvent("DOMUpdateBlockedPopups", true, true);
+    this.dispatchEvent(event);
+  }
+
+  retrieveListOfBlockedPopups() {
+    this.messageManager.sendAsyncMessage("PopupBlocking:GetBlockedPopupList", null);
+    return new Promise(resolve => {
+      let self = this;
+      this.messageManager.addMessageListener("PopupBlocking:ReplyGetBlockedPopupList",
+        function replyReceived(msg) {
+          self.messageManager.removeMessageListener("PopupBlocking:ReplyGetBlockedPopupList",
+            replyReceived);
+          resolve(msg.data.popupData);
+        }
+      );
+    });
+  }
+
+  unblockPopup(aPopupIndex) {
+    this.messageManager.sendAsyncMessage("PopupBlocking:UnblockPopup", { index: aPopupIndex });
+  }
+
+  audioPlaybackStarted() {
+    if (this._audioMuted) {
+      return;
+    }
+    let event = document.createEvent("Events");
+    event.initEvent("DOMAudioPlaybackStarted", true, false);
+    this.dispatchEvent(event);
+  }
 
-            try {
-                return {width: document.imageRequest.image.width, height: document.imageRequest.image.height };
-            } catch (e) {}
-            return null;
-          ]]>
-        </getter>
-      </property>
+  audioPlaybackStopped() {
+    let event = document.createEvent("Events");
+    event.initEvent("DOMAudioPlaybackStopped", true, false);
+    this.dispatchEvent(event);
+  }
 
-      <property name="isRemoteBrowser"
-                onget="return (this.getAttribute('remote') == 'true');"
-                readonly="true"/>
+  notifyGloballyAutoplayBlocked() {
+    let event = document.createEvent("CustomEvent");
+    event.initCustomEvent("GloballyAutoplayBlocked", true, false, {
+      url: this.documentURI,
+    });
+    this.dispatchEvent(event);
+  }
 
-      <property name="remoteType"
-                readonly="true">
-        <getter>
-          <![CDATA[
-            if (!this.isRemoteBrowser) {
-              return null;
-            }
+  /**
+   * When the pref "media.block-autoplay-until-in-foreground" is on,
+   * Gecko delays starting playback of media resources in tabs until the
+   * tab has been in the foreground or resumed by tab's play tab icon.
+   * - When Gecko delays starting playback of a media resource in a window,
+   * it sends a message to call activeMediaBlockStarted(). This causes the
+   * tab audio indicator to show.
+   * - When a tab is foregrounded, Gecko starts playing all delayed media
+   * resources in that tab, and sends a message to call
+   * activeMediaBlockStopped(). This causes the tab audio indicator to hide.
+   */
+  activeMediaBlockStarted() {
+    this._hasAnyPlayingMediaBeenBlocked = true;
+    let event = document.createEvent("Events");
+    event.initEvent("DOMAudioPlaybackBlockStarted", true, false);
+    this.dispatchEvent(event);
+  }
 
-            let remoteType = this.getAttribute("remoteType");
-            if (remoteType) {
-              return remoteType;
-            }
+  activeMediaBlockStopped() {
+    if (!this._hasAnyPlayingMediaBeenBlocked) {
+      return;
+    }
+    this._hasAnyPlayingMediaBeenBlocked = false;
+    let event = document.createEvent("Events");
+    event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
+    this.dispatchEvent(event);
+  }
 
-            let E10SUtils = ChromeUtils.import("resource://gre/modules/E10SUtils.jsm", {}).E10SUtils;
-            return E10SUtils.DEFAULT_REMOTE_TYPE;
-          ]]>
-        </getter>
-      </property>
+  mute(transientState) {
+    if (!transientState) {
+      this._audioMuted = true;
+    }
+    this.messageManager.sendAsyncMessage("AudioPlayback", { type: "mute" });
+  }
+
+  unmute() {
+    this._audioMuted = false;
+    this.messageManager.sendAsyncMessage("AudioPlayback", { type: "unmute" });
+  }
+
+  pauseMedia(disposable) {
+    let suspendedReason;
+    if (disposable) {
+      suspendedReason = "mediaControlPaused";
+    } else {
+      suspendedReason = "lostAudioFocusTransiently";
+    }
+
+    this.messageManager.sendAsyncMessage("AudioPlayback", { type: suspendedReason });
+  }
+
+  stopMedia() {
+    this.messageManager.sendAsyncMessage("AudioPlayback", { type: "mediaControlStopped" });
+  }
 
-      <property name="messageManager"
-                readonly="true">
-        <getter>
-          <![CDATA[
-            if (this.frameLoader) {
-              return this.frameLoader.messageManager;
-            }
-            return null;
-          ]]>
-        </getter>
+  resumeMedia() {
+    this.messageManager.sendAsyncMessage("AudioPlayback", { type: "resumeMedia" });
+    if (this._hasAnyPlayingMediaBeenBlocked) {
+      this._hasAnyPlayingMediaBeenBlocked = false;
+      let event = document.createEvent("Events");
+      event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
+      this.dispatchEvent(event);
+    }
+  }
+
+  unselectedTabHover(hovered) {
+    if (!this._shouldSendUnselectedTabHover) {
+      return;
+    }
+    this.messageManager.sendAsyncMessage("Browser:UnselectedTabHover", { hovered });
+  }
 
-      </property>
+  didStartLoadSinceLastUserTyping() {
+    return !this.inLoadURI &&
+      this.urlbarChangeTracker._startedLoadSinceLastUserTyping;
+  }
 
-      <field name="_webBrowserFind">null</field>
+  construct() {
+    if (this.isRemoteBrowser) {
+      /*
+       * Don't try to send messages from this function. The message manager for
+       * the <browser> element may not be initialized yet.
+       */
+
+      this._remoteWebNavigation = Cc["@mozilla.org/remote-web-navigation;1"]
+        .createInstance(Ci.nsIWebNavigation);
+      this._remoteWebNavigationImpl = this._remoteWebNavigation.wrappedJSObject;
+      this._remoteWebNavigationImpl.swapBrowser(this);
 
-      <property name="webBrowserFind"
-                readonly="true">
-        <getter>
-        <![CDATA[
-          if (!this._webBrowserFind)
-            this._webBrowserFind = this.docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebBrowserFind);
-          return this._webBrowserFind;
-        ]]>
-        </getter>
-      </property>
+      // Initialize contentPrincipal to the about:blank principal for this loadcontext
+      let { Services } = ChromeUtils.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);
+
+      // browser-child messages, such as Content:LocationChange, are handled in
+      // RemoteWebProgress, ensure it is loaded and ready.
+      let jsm = "resource://gre/modules/RemoteWebProgress.jsm";
+      let { RemoteWebProgressManager } = ChromeUtils.import(jsm, {});
+
+      let oldManager = this._remoteWebProgressManager;
+      this._remoteWebProgressManager = new RemoteWebProgressManager(this);
+      if (oldManager) {
+        // We're transitioning from one remote type to another. This means that
+        // the RemoteWebProgress listener is listening to the old message manager,
+        // and needs to be pointed at the new one.
+        this._remoteWebProgressManager.swapListeners(oldManager);
+      }
+
+      this._remoteWebProgress = this._remoteWebProgressManager.topLevelWebProgress;
+
+      this.messageManager.loadFrameScript("chrome://global/content/browser-child.js", true);
 
-      <method name="getTabBrowser">
-        <body>
-          <![CDATA[
-            if (this.ownerGlobal.gBrowser &&
-                this.ownerGlobal.gBrowser.getTabForBrowser &&
-                this.ownerGlobal.gBrowser.getTabForBrowser(this)) {
-              return this.ownerGlobal.gBrowser;
-            }
-            return null;
-          ]]>
-        </body>
-      </method>
+      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);
+    }
 
-      <field name="_finder">null</field>
-      <field name="_remoteFinder">null</field>
-      <property name="finder" readonly="true">
-        <getter><![CDATA[
-          if (this.isRemoteBrowser) {
-            if (!this._remoteFinder) {
-              // Don't attempt to create the remote finder if the
-              // messageManager has already gone away
-              if (!this.messageManager)
-                return null;
+    try {
+      // |webNavigation.sessionHistory| will have been set by the frame
+      // loader when creating the docShell as long as this xul:browser
+      // doesn't have the 'disablehistory' attribute set.
+      if (this.docShell && this.webNavigation.sessionHistory) {
+        var os = Cc["@mozilla.org/observer-service;1"]
+          .getService(Ci.nsIObserverService);
+        os.addObserver(this, "browser:purge-session-history", true);
 
-              let jsm = "resource://gre/modules/FinderParent.jsm";
-              let { FinderParent } = ChromeUtils.import(jsm, {});
-              this._remoteFinder = new FinderParent(this);
-            }
-            return this._remoteFinder;
+        // enable global history if we weren't told otherwise
+        if (!this.hasAttribute("disableglobalhistory") && !this.isRemoteBrowser) {
+          try {
+            this.docShell.useGlobalHistory = true;
+          } catch (ex) {
+            // This can occur if the Places database is locked
+            Cu.reportError("Error enabling browser global history: " + ex);
           }
-          if (!this._finder) {
-            if (!this.docShell)
-              return null;
+        }
+      }
+    } catch (e) {
+      Cu.reportError(e);
+    }
+    try {
+      // Ensures the securityUI is initialized.
+      var securityUI = this.securityUI; // eslint-disable-line no-unused-vars
+    } catch (e) {}
+
+    // tabbrowser.xml sets "sameProcessAsFrameLoader" as a direct property
+    // on some browsers before they are put into a DOM (and get a
+    // binding).  This hack makes sure that we hold a weak reference to
+    // the other browser (and go through the proper getter and setter).
+    if (this.hasOwnProperty("sameProcessAsFrameLoader")) {
+      var sameProcessAsFrameLoader = this.sameProcessAsFrameLoader;
+      delete this.sameProcessAsFrameLoader;
+      this.sameProcessAsFrameLoader = sameProcessAsFrameLoader;
+    }
 
-            let Finder = ChromeUtils.import("resource://gre/modules/Finder.jsm", {}).Finder;
-            this._finder = new Finder(this.docShell);
-          }
-          return this._finder;
-        ]]></getter>
-      </property>
+    if (!this.isRemoteBrowser) {
+      // If we've transitioned from remote to non-remote, we'll give up trying to
+      // keep the web progress listeners persisted during the transition.
+      delete this._remoteWebProgressManager;
+      delete this._remoteWebProgress;
+
+      this.addEventListener("pagehide", this.onPageHide, true);
+    }
+
+    if (this.messageManager) {
+      this.messageManager.addMessageListener("PopupBlocking:UpdateBlockedPopups", this);
+      this.messageManager.addMessageListener("Autoscroll:Start", this);
+      this.messageManager.addMessageListener("Autoscroll:Cancel", this);
+      this.messageManager.addMessageListener("AudioPlayback:Start", this);
+      this.messageManager.addMessageListener("AudioPlayback:Stop", this);
+      this.messageManager.addMessageListener("AudioPlayback:ActiveMediaBlockStart", this);
+      this.messageManager.addMessageListener("AudioPlayback:ActiveMediaBlockStop", this);
+      this.messageManager.addMessageListener("UnselectedTabHover:Toggle", this);
+      this.messageManager.addMessageListener("GloballyAutoplayBlocked", this);
 
-      <field name="_fastFind">null</field>
-      <property name="fastFind" readonly="true">
-        <getter><![CDATA[
-          if (!this._fastFind) {
-            if (!("@mozilla.org/typeaheadfind;1" in Cc))
-              return null;
+      if (this.hasAttribute("selectmenulist")) {
+        this.messageManager.addMessageListener("Forms:ShowDropDown", this);
+        this.messageManager.addMessageListener("Forms:HideDropDown", this);
+      }
 
-            var tabBrowser = this.getTabBrowser();
-            if (tabBrowser && "fastFind" in tabBrowser)
-              return this._fastFind = tabBrowser.fastFind;
+    }
+  }
 
-            if (!this.docShell)
-              return null;
+  /**
+   * 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.
+   */
+  destroy() {
+    // 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;
 
-            this._fastFind = Cc["@mozilla.org/typeaheadfind;1"]
-                               .createInstance(Ci.nsITypeAheadFind);
-            this._fastFind.init(this.docShell);
-          }
-          return this._fastFind;
-        ]]></getter>
-      </property>
+    if (this.isRemoteBrowser) {
+      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 = ChromeUtils.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.
+        }
+      }
+
+      return;
+    }
 
-      <field name="_outerWindowID">null</field>
-      <property name="outerWindowID" readonly="true">
-        <getter><![CDATA[
-          if (this.isRemoteBrowser) {
-            return this._outerWindowID;
-          }
-          return this.docShell.outerWindowID;
-        ]]></getter>
-      </property>
+    if (this.docShell && this.webNavigation.sessionHistory) {
+      var os = Cc["@mozilla.org/observer-service;1"]
+        .getService(Ci.nsIObserverService);
+      try {
+        os.removeObserver(this, "browser:purge-session-history");
+      } catch (ex) {
+        // It's not clear why this sometimes throws an exception.
+      }
+    }
+
+    this._fastFind = null;
+    this._webBrowserFind = null;
+
+    this.lastURI = null;
+
+    if (!this.isRemoteBrowser) {
+      this.removeEventListener("pagehide", this.onPageHide, true);
+    }
 
-      <field name="_innerWindowID">null</field>
-      <property name="innerWindowID" readonly="true">
-        <getter><![CDATA[
-          if (this.isRemoteBrowser) {
-            return this._innerWindowID;
+    if (this._autoScrollNeedsCleanup) {
+      // we polluted the global scope, so clean it up
+      this._autoScrollPopup.remove();
+    }
+  }
+
+  /**
+   * We call this _receiveMessage (and alias receiveMessage to it) so that
+   * bindings that inherit from this one can delegate to it.
+   */
+  _receiveMessage(aMessage) {
+    let data = aMessage.data;
+    switch (aMessage.name) {
+      case "PopupBlocking:UpdateBlockedPopups":
+        {
+          this.blockedPopups = {
+            length: data.count,
+            reported: !data.freshPopup,
+          };
+
+          this.updateBlockedPopups();
+          break;
+        }
+      case "Autoscroll:Start":
+        {
+          if (!this.autoscrollEnabled) {
+            return { autoscrollEnabled: false, usingApz: false };
           }
-          try {
-            return this.contentWindow.windowUtils.currentInnerWindowID;
-          } catch (e) {
-            if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
-              throw e;
+          this.startScroll(data.scrolldir, data.screenX, data.screenY);
+          let usingApz = false;
+          if (this.isRemoteBrowser && data.scrollId != null &&
+            this.mPrefs.getBoolPref("apz.autoscroll.enabled", false)) {
+            let { tabParent } = this.frameLoader;
+            if (tabParent) {
+              // If APZ is handling the autoscroll, it may decide to cancel
+              // it of its own accord, so register an observer to allow it
+              // to notify us of that.
+              var os = Cc["@mozilla.org/observer-service;1"]
+                .getService(Ci.nsIObserverService);
+              os.addObserver(this, "apz:cancel-autoscroll", true);
+
+              usingApz = tabParent.startApzAutoscroll(
+                data.screenX, data.screenY,
+                data.scrollId, data.presShellId);
             }
-            return null;
+            // Save the IDs for later
+            this._autoScrollScrollId = data.scrollId;
+            this._autoScrollPresShellId = data.presShellId;
           }
-        ]]></getter>
-      </property>
-
-      <field name="_browsingContextId">null</field>
-
-      <property name="browsingContext" readonly="true">
-        <getter><![CDATA[
-          if (!this.isRemoteBrowser) {
-            return this.docShell.browsingContext;
+          return { autoscrollEnabled: true, usingApz };
+        }
+      case "Autoscroll:Cancel":
+        this._autoScrollPopup.hidePopup();
+        break;
+      case "AudioPlayback:Start":
+        this.audioPlaybackStarted();
+        break;
+      case "AudioPlayback:Stop":
+        this.audioPlaybackStopped();
+        break;
+      case "AudioPlayback:ActiveMediaBlockStart":
+        this.activeMediaBlockStarted();
+        break;
+      case "AudioPlayback:ActiveMediaBlockStop":
+        this.activeMediaBlockStopped();
+        break;
+      case "UnselectedTabHover:Toggle":
+        this._shouldSendUnselectedTabHover = data.enable ?
+          ++this._unselectedTabHoverMessageListenerCount > 0 :
+          --this._unselectedTabHoverMessageListenerCount == 0;
+        break;
+      case "GloballyAutoplayBlocked":
+        this.notifyGloballyAutoplayBlocked();
+        break;
+      case "Forms:ShowDropDown":
+        {
+          if (!this._selectParentHelper) {
+            this._selectParentHelper =
+              ChromeUtils.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
           }
 
-          return ChromeUtils.getBrowsingContext(this._browsingContextId);
-        ]]></getter>
-      </property>
-
-      <field name="_lastSearchString">null</field>
-
-
-      <field name="_controller">null</field>
-
-      <field name="_selectParentHelper">null</field>
-
-      <field name="_remoteWebNavigation">null</field>
-      <!-- Note that this overrides webNavigation on XULFrameElement, and duplicates the return value for the non-remote case -->
-      <property name="webNavigation"
-                readonly="true"
-                onget="return this.isRemoteBrowser ? this._remoteWebNavigation : this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);"/>
-
-      <field name="_remoteWebProgress">null</field>
-      <property name="webProgress"
-                readonly="true"
-                onget="return this.isRemoteBrowser ? this._remoteWebProgress : this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);"/>
-
-      <property name="sessionHistory"
-                onget="return this.webNavigation.sessionHistory;"
-                readonly="true"/>
-
-      <property name="markupDocumentViewer"
-                onget="return this.docShell.contentViewer;"
-                readonly="true"/>
-
-      <field name="_contentTitle">""</field>
-      <property name="contentTitle"
-                onget="return this.isRemoteBrowser ? this._contentTitle : this.contentDocument.title;"
-                readonly="true"/>
-
-      <field name="_characterSet">""</field>
-      <property name="characterSet"
-                onget="return this.isRemoteBrowser ? this._characterSet : this.docShell.charset;">
-        <setter><![CDATA[
-          if (this.isRemoteBrowser) {
-            this.messageManager.sendAsyncMessage("UpdateCharacterSet", {value: val});
-            this._characterSet = val;
-          } else {
-            this.docShell.charset = val;
-            this.docShell.gatherCharsetMenuTelemetry();
-          }
-        ]]></setter>
-      </property>
-
-      <field name="_mayEnableCharacterEncodingMenu">null</field>
-      <property name="mayEnableCharacterEncodingMenu"
-                onget="return this.isRemoteBrowser ? this._mayEnableCharacterEncodingMenu : this.docShell.mayEnableCharacterEncodingMenu;"
-                readonly="true"/>
-
-      <field name="_contentPrincipal">null</field>
-      <property name="contentPrincipal"
-                onget="return this.isRemoteBrowser ? this._contentPrincipal : this.contentDocument.nodePrincipal;"
-                readonly="true"/>
-
-      <field name="_contentRequestContextID">null</field>
-      <property name="contentRequestContextID" readonly="true">
-        <getter><![CDATA[
-          if (this.isRemoteBrowser) {
-            return this._contentRequestContextID;
-          }
-          try {
-            return this.contentDocument.documentLoadGroup
-                       .requestContextID;
-          } catch (e) {
-            return null;
-          }
-        ]]></getter>
-      </property>
-
-      <property name="showWindowResizer"
-                onset="if (val) this.setAttribute('showresizer', 'true');
-                       else this.removeAttribute('showresizer');
-                       return val;"
-                onget="return this.getAttribute('showresizer') == 'true';"/>
-
-      <field name="_fullZoom">1</field>
-      <property name="fullZoom">
-        <getter><![CDATA[
-          if (this.isRemoteBrowser) {
-            return this._fullZoom;
-          }
-          return this.markupDocumentViewer.fullZoom;
-        ]]></getter>
-        <setter><![CDATA[
-          if (this.isRemoteBrowser) {
-            let changed = val.toFixed(2) != this._fullZoom.toFixed(2);
-
-            if (changed) {
-              this._fullZoom = val;
-              try {
-                this.messageManager.sendAsyncMessage("FullZoom", {value: val});
-              } catch (ex) {}
-
-              let event = new Event("FullZoomChange", {bubbles: true});
-              this.dispatchEvent(event);
-            }
-          } else {
-            this.markupDocumentViewer.fullZoom = val;
-          }
-        ]]></setter>
-      </property>
-
-      <field name="_textZoom">1</field>
-      <property name="textZoom">
-        <getter><![CDATA[
-          if (this.isRemoteBrowser) {
-            return this._textZoom;
-          }
-          return this.markupDocumentViewer.textZoom;
-        ]]></getter>
-        <setter><![CDATA[
-          if (this.isRemoteBrowser) {
-            let changed = val.toFixed(2) != this._textZoom.toFixed(2);
-
-            if (changed) {
-              this._textZoom = val;
-              try {
-                this.messageManager.sendAsyncMessage("TextZoom", {value: val});
-              } catch (ex) {}
-
-              let event = new Event("TextZoomChange", {bubbles: true});
-              this.dispatchEvent(event);
-            }
-          } else {
-            this.markupDocumentViewer.textZoom = val;
-          }
-
-        ]]></setter>
-        <setter><![CDATA[
-        ]]></setter>
-      </property>
-
-      <field name="_isSyntheticDocument">false</field>
-      <property name="isSyntheticDocument">
-        <getter><![CDATA[
-          if (this.isRemoteBrowser) {
-            return this._isSyntheticDocument;
-          }
-          return this.contentDocument.mozSyntheticDocument;
-        ]]></getter>
-      </property>
-
-      <property name="hasContentOpener">
-        <getter><![CDATA[
-          if (this.isRemoteBrowser) {
-            return this.frameLoader.tabParent.hasContentOpener;
-          }
-          return !!this.contentWindow.opener;
-        ]]></getter>
-      </property>
-
-      <field name="mPrefs" readonly="true">
-        Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
-      </field>
-
-      <field name="_mStrBundle">null</field>
-
-      <property name="mStrBundle">
-        <getter>
-        <![CDATA[
-          if (!this._mStrBundle) {
-            // need to create string bundle manually instead of using <xul:stringbundle/>
-            // see bug 63370 for details
-            this._mStrBundle = Cc["@mozilla.org/intl/stringbundle;1"]
-                                 .getService(Ci.nsIStringBundleService)
-                                 .createBundle("chrome://global/locale/browser.properties");
-          }
-          return this._mStrBundle;
-        ]]></getter>
-      </property>
-
-      <method name="addProgressListener">
-        <parameter name="aListener"/>
-        <parameter name="aNotifyMask"/>
-        <body>
-          <![CDATA[
-            if (!aNotifyMask) {
-              aNotifyMask = Ci.nsIWebProgress.NOTIFY_ALL;
-            }
-            this.webProgress.addProgressListener(aListener, aNotifyMask);
-          ]]>
-        </body>
-      </method>
-
-      <method name="removeProgressListener">
-        <parameter name="aListener"/>
-        <body>
-          <![CDATA[
-            this.webProgress.removeProgressListener(aListener);
-         ]]>
-        </body>
-      </method>
-
-      <method name="onPageHide">
-        <parameter name="aEvent"/>
-        <body>
-          <![CDATA[
-            if (!this.docShell || !this.fastFind)
-              return;
-            var tabBrowser = this.getTabBrowser();
-            if (!tabBrowser || !("fastFind" in tabBrowser) ||
-                tabBrowser.selectedBrowser == this)
-              this.fastFind.setDocShell(this.docShell);
-         ]]>
-        </body>
-      </method>
-
-      <method name="updateBlockedPopups">
-        <body>
-          <![CDATA[
-            let event = document.createEvent("Events");
-            event.initEvent("DOMUpdateBlockedPopups", true, true);
-            this.dispatchEvent(event);
-          ]]>
-        </body>
-      </method>
-
-      <method name="retrieveListOfBlockedPopups">
-        <body>
-          <![CDATA[
-          this.messageManager.sendAsyncMessage("PopupBlocking:GetBlockedPopupList", null);
-          return new Promise(resolve => {
-            let self = this;
-            this.messageManager.addMessageListener("PopupBlocking:ReplyGetBlockedPopupList",
-              function replyReceived(msg) {
-                self.messageManager.removeMessageListener("PopupBlocking:ReplyGetBlockedPopupList",
-                                                          replyReceived);
-                resolve(msg.data.popupData);
-              }
-            );
-          });
-          ]]>
-        </body>
-      </method>
-
-      <method name="unblockPopup">
-        <parameter name="aPopupIndex"/>
-        <body><![CDATA[
-          this.messageManager.sendAsyncMessage("PopupBlocking:UnblockPopup",
-                                               {index: aPopupIndex});
-        ]]></body>
-      </method>
-
-      <field name="blockedPopups">null</field>
-
-      <method name="audioPlaybackStarted">
-        <body>
-          <![CDATA[
-            if (this._audioMuted) {
-              return;
-            }
-            let event = document.createEvent("Events");
-            event.initEvent("DOMAudioPlaybackStarted", true, false);
-            this.dispatchEvent(event);
-          ]]>
-        </body>
-      </method>
-
-      <method name="audioPlaybackStopped">
-        <body>
-          <![CDATA[
-            let event = document.createEvent("Events");
-            event.initEvent("DOMAudioPlaybackStopped", true, false);
-            this.dispatchEvent(event);
-          ]]>
-        </body>
-      </method>
-
-      <method name="notifyGloballyAutoplayBlocked">
-        <body>
-          <![CDATA[
-            let event = document.createEvent("CustomEvent");
-            event.initCustomEvent("GloballyAutoplayBlocked", true, false,
-              {
-                url: this.documentURI,
-              });
-            this.dispatchEvent(event);
-          ]]>
-        </body>
-      </method>
-
-      <!--
-        When the pref "media.block-autoplay-until-in-foreground" is on,
-        Gecko delays starting playback of media resources in tabs until the
-        tab has been in the foreground or resumed by tab's play tab icon.
-        - When Gecko delays starting playback of a media resource in a window,
-        it sends a message to call activeMediaBlockStarted(). This causes the
-        tab audio indicator to show.
-        - When a tab is foregrounded, Gecko starts playing all delayed media
-        resources in that tab, and sends a message to call
-        activeMediaBlockStopped(). This causes the tab audio indicator to hide.
-       -->
-      <method name="activeMediaBlockStarted">
-        <body>
-          <![CDATA[
-            this._hasAnyPlayingMediaBeenBlocked = true;
-            let event = document.createEvent("Events");
-            event.initEvent("DOMAudioPlaybackBlockStarted", true, false);
-            this.dispatchEvent(event);
-          ]]>
-        </body>
-      </method>
-
-      <method name="activeMediaBlockStopped">
-        <body>
-          <![CDATA[
-            if (!this._hasAnyPlayingMediaBeenBlocked) {
-              return;
-            }
-            this._hasAnyPlayingMediaBeenBlocked = false;
-            let event = document.createEvent("Events");
-            event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
-            this.dispatchEvent(event);
-          ]]>
-        </body>
-      </method>
+          let menulist = document.getElementById(this.getAttribute("selectmenulist"));
+          menulist.menupopup.style.direction = data.direction;
+          this._selectParentHelper.populate(menulist, data.options, data.selectedIndex, this._fullZoom,
+            data.uaSelectBackgroundColor, data.uaSelectColor,
+            data.selectBackgroundColor, data.selectColor, data.selectTextShadow);
+          this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
+          break;
+        }
 
-      <field name="_audioMuted">false</field>
-      <property name="audioMuted"
-                onget="return this._audioMuted;"
-                readonly="true"/>
-
-      <field name="_hasAnyPlayingMediaBeenBlocked">false</field>
-
-      <method name="mute">
-        <parameter name="transientState"/>
-        <body>
-          <![CDATA[
-            if (!transientState) {
-              this._audioMuted = true;
-            }
-            this.messageManager.sendAsyncMessage("AudioPlayback",
-                                                 {type: "mute"});
-          ]]>
-        </body>
-      </method>
-
-      <method name="unmute">
-        <body>
-          <![CDATA[
-            this._audioMuted = false;
-            this.messageManager.sendAsyncMessage("AudioPlayback",
-                                                 {type: "unmute"});
-          ]]>
-        </body>
-      </method>
-
-      <method name="pauseMedia">
-        <parameter name="disposable"/>
-        <body>
-          <![CDATA[
-            let suspendedReason;
-            if (disposable) {
-              suspendedReason = "mediaControlPaused";
-            } else {
-              suspendedReason = "lostAudioFocusTransiently";
-            }
-
-            this.messageManager.sendAsyncMessage("AudioPlayback",
-                                                 {type: suspendedReason});
-          ]]>
-        </body>
-      </method>
-
-      <method name="stopMedia">
-        <body>
-          <![CDATA[
-            this.messageManager.sendAsyncMessage("AudioPlayback",
-                                                 {type: "mediaControlStopped"});
-          ]]>
-        </body>
-      </method>
-
-      <method name="resumeMedia">
-        <body>
-          <![CDATA[
-            this.messageManager.sendAsyncMessage("AudioPlayback",
-                                                 {type: "resumeMedia"});
-            if (this._hasAnyPlayingMediaBeenBlocked) {
-              this._hasAnyPlayingMediaBeenBlocked = false;
-              let event = document.createEvent("Events");
-              event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
-              this.dispatchEvent(event);
-            }
-          ]]>
-        </body>
-      </method>
-
-      <!--
-        Only send the message "Browser:UnselectedTabHover" when someone requests
-        for the message, which can reduce non-necessary communication.
-      -->
-      <field name="_shouldSendUnselectedTabHover">false</field>
-      <field name="_unselectedTabHoverMessageListenerCount">0</field>
-      <property name="shouldHandleUnselectedTabHover"
-                onget="return this._shouldSendUnselectedTabHover;"
-                readonly="true"/>
-
-      <method name="unselectedTabHover">
-        <parameter name="hovered"/>
-        <body>
-          <![CDATA[
-            if (!this._shouldSendUnselectedTabHover) {
-              return;
-            }
-            this.messageManager.sendAsyncMessage("Browser:UnselectedTabHover",
-              { hovered });
-          ]]>
-        </body>
-      </method>
-
-      <field name="_securityUI">null</field>
-      <property name="securityUI"
-                readonly="true">
-        <getter>
-          <![CDATA[
-            if (this.isRemoteBrowser) {
-              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 = ChromeUtils.import(jsm, {}).RemoteSecurityUI;
-                this._securityUI = new RemoteSecurityUI();
-              }
-
-              // We want to double-wrap the JS implemented interface, so that QI and instanceof works.
-              var ptr = Cc["@mozilla.org/supports-interface-pointer;1"]
-                          .createInstance(Ci.nsISupportsInterfacePointer);
-              ptr.data = this._securityUI;
-              return ptr.data.QueryInterface(Ci.nsISecureBrowserUI);
-            }
-
-            if (!this.docShell.securityUI) {
-              const SECUREBROWSERUI_CONTRACTID = "@mozilla.org/secure_browser_ui;1";
-              var securityUI = Cc[SECUREBROWSERUI_CONTRACTID]
-                                 .createInstance(Ci.nsISecureBrowserUI);
-              securityUI.init(this.docShell);
-            }
-
-            return this.docShell.securityUI;
-          ]]>
-        </getter>
-      </property>
-
-      <field name="urlbarChangeTracker">
-        ({
-          _startedLoadSinceLastUserTyping: false,
-
-          startedLoad() {
-            this._startedLoadSinceLastUserTyping = true;
-          },
-          finishedLoad() {
-            this._startedLoadSinceLastUserTyping = false;
-          },
-          userTyped() {
-            this._startedLoadSinceLastUserTyping = false;
-          },
-        })
-      </field>
-
-      <method name="didStartLoadSinceLastUserTyping">
-        <body><![CDATA[
-          return !this.inLoadURI &&
-                 this.urlbarChangeTracker._startedLoadSinceLastUserTyping;
-        ]]></body>
-      </method>
-
-      <field name="_userTypedValue">
-        null
-      </field>
-
-      <property name="userTypedValue"
-                onget="return this._userTypedValue;">
-        <setter><![CDATA[
-          this.urlbarChangeTracker.userTyped();
-          this._userTypedValue = val;
-          return val;
-        ]]></setter>
-      </property>
-
-      <field name="droppedLinkHandler">
-        null
-      </field>
-
-      <field name="mIconURL">null</field>
-
-      <!-- This is managed by the tabbrowser -->
-      <field name="lastURI">null</field>
-
-      <field name="mDestroyed">false</field>
-
-      <constructor>
-        <![CDATA[
-        this.construct();
-        ]]>
-      </constructor>
-
-      <method name="construct">
-        <body><![CDATA[
-          if (this.isRemoteBrowser) {
-            /*
-            * Don't try to send messages from this function. The message manager for
-            * the <browser> element may not be initialized yet.
-            */
-
-            this._remoteWebNavigation = Cc["@mozilla.org/remote-web-navigation;1"]
-                                          .createInstance(Ci.nsIWebNavigation);
-            this._remoteWebNavigationImpl = this._remoteWebNavigation.wrappedJSObject;
-            this._remoteWebNavigationImpl.swapBrowser(this);
-
-            // Initialize contentPrincipal to the about:blank principal for this loadcontext
-            let {Services} = ChromeUtils.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);
-
-            // browser-child messages, such as Content:LocationChange, are handled in
-            // RemoteWebProgress, ensure it is loaded and ready.
-            let jsm = "resource://gre/modules/RemoteWebProgress.jsm";
-            let { RemoteWebProgressManager } = ChromeUtils.import(jsm, {});
-
-            let oldManager = this._remoteWebProgressManager;
-            this._remoteWebProgressManager = new RemoteWebProgressManager(this);
-            if (oldManager) {
-              // We're transitioning from one remote type to another. This means that
-              // the RemoteWebProgress listener is listening to the old message manager,
-              // and needs to be pointed at the new one.
-              this._remoteWebProgressManager.swapListeners(oldManager);
-            }
-
-            this._remoteWebProgress = this._remoteWebProgressManager.topLevelWebProgress;
-
-            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);
-          }
-
-          try {
-            // |webNavigation.sessionHistory| will have been set by the frame
-            // loader when creating the docShell as long as this xul:browser
-            // doesn't have the 'disablehistory' attribute set.
-            if (this.docShell && this.webNavigation.sessionHistory) {
-              var os = Cc["@mozilla.org/observer-service;1"]
-                         .getService(Ci.nsIObserverService);
-              os.addObserver(this, "browser:purge-session-history", true);
-
-              // enable global history if we weren't told otherwise
-              if (!this.hasAttribute("disableglobalhistory") && !this.isRemoteBrowser) {
-                try {
-                  this.docShell.useGlobalHistory = true;
-                } catch (ex) {
-                  // This can occur if the Places database is locked
-                  Cu.reportError("Error enabling browser global history: " + ex);
-                }
-              }
-            }
-          } catch (e) {
-            Cu.reportError(e);
-          }
-          try {
-            // Ensures the securityUI is initialized.
-            var securityUI = this.securityUI; // eslint-disable-line no-unused-vars
-          } catch (e) {
-          }
-
-          // tabbrowser.xml sets "sameProcessAsFrameLoader" as a direct property
-          // on some browsers before they are put into a DOM (and get a
-          // binding).  This hack makes sure that we hold a weak reference to
-          // the other browser (and go through the proper getter and setter).
-          if (this.hasOwnProperty("sameProcessAsFrameLoader")) {
-            var sameProcessAsFrameLoader = this.sameProcessAsFrameLoader;
-            delete this.sameProcessAsFrameLoader;
-            this.sameProcessAsFrameLoader = sameProcessAsFrameLoader;
-          }
-
-          if (!this.isRemoteBrowser) {
-            // If we've transitioned from remote to non-remote, we'll give up trying to
-            // keep the web progress listeners persisted during the transition.
-            delete this._remoteWebProgressManager;
-            delete this._remoteWebProgress;
-
-            this.addEventListener("pagehide", this.onPageHide, true);
-          }
-
-          if (this.messageManager) {
-            this.messageManager.addMessageListener("PopupBlocking:UpdateBlockedPopups", this);
-            this.messageManager.addMessageListener("Autoscroll:Start", this);
-            this.messageManager.addMessageListener("Autoscroll:Cancel", this);
-            this.messageManager.addMessageListener("AudioPlayback:Start", this);
-            this.messageManager.addMessageListener("AudioPlayback:Stop", this);
-            this.messageManager.addMessageListener("AudioPlayback:ActiveMediaBlockStart", this);
-            this.messageManager.addMessageListener("AudioPlayback:ActiveMediaBlockStop", this);
-            this.messageManager.addMessageListener("UnselectedTabHover:Toggle", this);
-            this.messageManager.addMessageListener("GloballyAutoplayBlocked", this);
-
-            if (this.hasAttribute("selectmenulist")) {
-              this.messageManager.addMessageListener("Forms:ShowDropDown", this);
-              this.messageManager.addMessageListener("Forms:HideDropDown", this);
-            }
-
-          }
-        ]]></body>
-      </method>
-
-      <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.-->
-      <method name="destroy">
-        <body><![CDATA[
-          // Make sure that any open select is closed.
+      case "Forms:HideDropDown":
+        {
           if (this._selectParentHelper) {
             let menulist = document.getElementById(this.getAttribute("selectmenulist"));
             this._selectParentHelper.hide(menulist, this);
           }
-          if (this.mDestroyed)
-            return;
-          this.mDestroyed = true;
+          break;
+        }
 
-          if (this.isRemoteBrowser) {
-            try {
-              this.controllers.removeController(this._controller);
-            } catch (ex) {
-              // This can fail when this browser element is not attached to a
-              // BrowserDOMWindow.
-            }
+    }
+    return undefined;
+  }
 
-            if (!this.hasAttribute("disablehistory")) {
-              let Services = ChromeUtils.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.
-              }
-            }
+  receiveMessage(aMessage) {
+    if (!this.isRemoteBrowser) {
+      return this._receiveMessage(aMessage);
+    }
 
-            return;
-          }
+    let data = aMessage.data;
+    switch (aMessage.name) {
+      case "Browser:Init":
+        this._outerWindowID = data.outerWindowID;
+        this._browsingContextId = data.browsingContextId;
+        break;
+      case "DOMTitleChanged":
+        this._contentTitle = data.title;
+        break;
+      case "ImageDocumentLoaded":
+        this._imageDocument = {
+          width: data.width,
+          height: data.height,
+        };
+        break;
 
-          if (this.docShell && this.webNavigation.sessionHistory) {
-            var os = Cc["@mozilla.org/observer-service;1"]
-                       .getService(Ci.nsIObserverService);
-            try {
-              os.removeObserver(this, "browser:purge-session-history");
-            } catch (ex) {
-              // It's not clear why this sometimes throws an exception.
-            }
-          }
-
-          this._fastFind = null;
-          this._webBrowserFind = null;
-
-          this.lastURI = null;
-
-          if (!this.isRemoteBrowser) {
-            this.removeEventListener("pagehide", this.onPageHide, true);
+      case "Forms:ShowDropDown":
+        {
+          if (!this._selectParentHelper) {
+            this._selectParentHelper =
+              ChromeUtils.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
           }
 
-          if (this._autoScrollNeedsCleanup) {
-            // we polluted the global scope, so clean it up
-            this._autoScrollPopup.remove();
-          }
-        ]]></body>
-      </method>
+          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.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;
+        }
 
-      <!--
-        We call this _receiveMessage (and alias receiveMessage to it) so that
-        bindings that inherit from this one can delegate to it.
-      -->
-      <method name="_receiveMessage">
-        <parameter name="aMessage"/>
-        <body><![CDATA[
-          let data = aMessage.data;
-          switch (aMessage.name) {
-            case "PopupBlocking:UpdateBlockedPopups": {
-              this.blockedPopups = {
-                length: data.count,
-                reported: !data.freshPopup,
-              };
+      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;
+        }
+
+      default:
+        return this._receiveMessage(aMessage);
+    }
+    return undefined;
+
+  }
+
+  enableDisableCommandsRemoteOnly(aAction, aEnabledLength, aEnabledCommands, aDisabledLength, aDisabledCommands) {
+    if (this._controller) {
+      this._controller.enableDisableCommands(aAction,
+        aEnabledLength, aEnabledCommands,
+        aDisabledLength, aDisabledCommands);
+    }
+  }
 
-              this.updateBlockedPopups();
-              break;
-            }
-            case "Autoscroll:Start": {
-              if (!this.autoscrollEnabled) {
-                return {autoscrollEnabled: false, usingApz: false};
-              }
-              this.startScroll(data.scrolldir, data.screenX, data.screenY);
-              let usingApz = false;
-              if (this.isRemoteBrowser && data.scrollId != null &&
-                  this.mPrefs.getBoolPref("apz.autoscroll.enabled", false)) {
-                let { tabParent } = this.frameLoader;
-                if (tabParent) {
-                  // If APZ is handling the autoscroll, it may decide to cancel
-                  // it of its own accord, so register an observer to allow it
-                  // to notify us of that.
-                  var os = Cc["@mozilla.org/observer-service;1"]
-                             .getService(Ci.nsIObserverService);
-                  os.addObserver(this, "apz:cancel-autoscroll", true);
+  observe(aSubject, aTopic, aState) {
+    if (aTopic == "browser:purge-session-history") {
+      this.purgeSessionHistory();
+    } else if (aTopic == "apz:cancel-autoscroll") {
+      if (aState == this._autoScrollScrollId) {
+        // Set this._autoScrollScrollId to null, so in stopScroll() we
+        // don't call stopApzAutoscroll() (since it's APZ that
+        // initiated the stopping).
+        this._autoScrollScrollId = null;
+        this._autoScrollPresShellId = null;
+
+        this._autoScrollPopup.hidePopup();
+      }
+    }
+  }
+
+  purgeSessionHistory() {
+    if (this.isRemoteBrowser) {
+      try {
+        this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
+      } catch (ex) {
+        // This can throw if the browser has started to go away.
+        if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) {
+          throw ex;
+        }
+      }
+      this._remoteWebNavigationImpl.canGoBack = false;
+      this._remoteWebNavigationImpl.canGoForward = false;
+      return;
+    }
+    this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
+  }
 
-                  usingApz = tabParent.startApzAutoscroll(
-                      data.screenX, data.screenY,
-                      data.scrollId, data.presShellId);
-                }
-                // Save the IDs for later
-                this._autoScrollScrollId = data.scrollId;
-                this._autoScrollPresShellId = data.presShellId;
-              }
-              return {autoscrollEnabled: true, usingApz};
-            }
-            case "Autoscroll:Cancel":
-              this._autoScrollPopup.hidePopup();
-              break;
-            case "AudioPlayback:Start":
-              this.audioPlaybackStarted();
-              break;
-            case "AudioPlayback:Stop":
-              this.audioPlaybackStopped();
-              break;
-            case "AudioPlayback:ActiveMediaBlockStart":
-              this.activeMediaBlockStarted();
-              break;
-            case "AudioPlayback:ActiveMediaBlockStop":
-              this.activeMediaBlockStopped();
-              break;
-            case "UnselectedTabHover:Toggle":
-              this._shouldSendUnselectedTabHover = data.enable ?
-                ++this._unselectedTabHoverMessageListenerCount > 0 :
-                --this._unselectedTabHoverMessageListenerCount == 0;
-              break;
-            case "GloballyAutoplayBlocked":
-              this.notifyGloballyAutoplayBlocked();
-              break;
-            case "Forms:ShowDropDown": {
-              if (!this._selectParentHelper) {
-                this._selectParentHelper =
-                  ChromeUtils.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
-              }
+  createAboutBlankContentViewer(aPrincipal) {
+    if (this.isRemoteBrowser) {
+      // 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);
+      return;
+    }
+    let principal = BrowserUtils.principalWithMatchingOA(aPrincipal, this.contentPrincipal);
+    this.docShell.createAboutBlankContentViewer(principal);
+  }
+
+  stopScroll() {
+    if (this._scrolling) {
+      this._scrolling = false;
+      window.removeEventListener("mousemove", this, true);
+      window.removeEventListener("mousedown", this, true);
+      window.removeEventListener("mouseup", this, true);
+      window.removeEventListener("DOMMouseScroll", this, true);
+      window.removeEventListener("contextmenu", this, true);
+      window.removeEventListener("keydown", this, true);
+      window.removeEventListener("keypress", this, true);
+      window.removeEventListener("keyup", this, true);
+      this.messageManager.sendAsyncMessage("Autoscroll:Stop");
+
+      var os = Cc["@mozilla.org/observer-service;1"]
+        .getService(Ci.nsIObserverService);
+      try {
+        os.removeObserver(this, "apz:cancel-autoscroll");
+      } catch (ex) {
+        // It's not clear why this sometimes throws an exception
+      }
 
-              let menulist = document.getElementById(this.getAttribute("selectmenulist"));
-              menulist.menupopup.style.direction = data.direction;
-              this._selectParentHelper.populate(menulist, data.options, data.selectedIndex, this._fullZoom,
-                                                data.uaSelectBackgroundColor, data.uaSelectColor,
-                                                data.selectBackgroundColor, data.selectColor, data.selectTextShadow);
-              this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
-              break;
-            }
+      if (this.isRemoteBrowser && this._autoScrollScrollId != null) {
+        let { tabParent } = this.frameLoader;
+        if (tabParent) {
+          tabParent.stopApzAutoscroll(this._autoScrollScrollId,
+            this._autoScrollPresShellId);
+        }
+        this._autoScrollScrollId = null;
+        this._autoScrollPresShellId = null;
+      }
+    }
+  }
+
+  _createAutoScrollPopup() {
+    var popup = document.createXULElement("panel");
+    popup.className = "autoscroller";
+    // We set this attribute on the element so that mousemove
+    // events can be handled by browser-content.js.
+    popup.setAttribute("mousethrough", "always");
+    popup.setAttribute("consumeoutsideclicks", "true");
+    popup.setAttribute("rolluponmousewheel", "true");
+    popup.setAttribute("hidden", "true");
+    return popup;
+  }
 
-            case "Forms:HideDropDown": {
-              if (this._selectParentHelper) {
-                let menulist = document.getElementById(this.getAttribute("selectmenulist"));
-                this._selectParentHelper.hide(menulist, this);
-              }
-              break;
-            }
+  startScroll(scrolldir, screenX, screenY) {
+    const POPUP_SIZE = 32;
+    if (!this._autoScrollPopup) {
+      if (this.hasAttribute("autoscrollpopup")) {
+        // our creator provided a popup to share
+        this._autoScrollPopup = document.getElementById(this.getAttribute("autoscrollpopup"));
+      } else {
+        // we weren't provided a popup; we have to use the global scope
+        this._autoScrollPopup = this._createAutoScrollPopup();
+        document.documentElement.appendChild(this._autoScrollPopup);
+        this._autoScrollNeedsCleanup = true;
+      }
+      this._autoScrollPopup.removeAttribute("hidden");
+      this._autoScrollPopup.setAttribute("noautofocus", "true");
+      this._autoScrollPopup.style.height = POPUP_SIZE + "px";
+      this._autoScrollPopup.style.width = POPUP_SIZE + "px";
+      this._autoScrollPopup.style.margin = -POPUP_SIZE / 2 + "px";
+    }
 
-          }
-          return undefined;
-        ]]></body>
-      </method>
+    let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
+      .getService(Ci.nsIScreenManager);
+    let screen = screenManager.screenForRect(screenX, screenY, 1, 1);
+
+    // we need these attributes so themers don't need to create per-platform packages
+    if (screen.colorDepth > 8) { // need high color for transparency
+      // Exclude second-rate platforms
+      this._autoScrollPopup.setAttribute("transparent", !/BeOS|OS\/2/.test(navigator.appVersion));
+      // Enable translucency on Windows and Mac
+      this._autoScrollPopup.setAttribute("translucent", /Win|Mac/.test(navigator.platform));
+    }
 
-      <method name="receiveMessage">
-        <parameter name="aMessage"/>
-        <body><![CDATA[
-          if (!this.isRemoteBrowser) {
-            return this._receiveMessage(aMessage);
-          }
+    this._autoScrollPopup.setAttribute("scrolldir", scrolldir);
+    this._autoScrollPopup.addEventListener("popuphidden", this, true);
+
+    // Sanitize screenX/screenY for available screen size with half the size
+    // of the popup removed. The popup uses negative margins to center on the
+    // coordinates we pass. Unfortunately `window.screen.availLeft` can be negative
+    // on Windows in multi-monitor setups, so we use nsIScreenManager instead:
+    let left = {},
+      top = {},
+      width = {},
+      height = {};
+    screen.GetAvailRect(left, top, width, height);
 
-          let data = aMessage.data;
-          switch (aMessage.name) {
-            case "Browser:Init":
-              this._outerWindowID = data.outerWindowID;
-              this._browsingContextId = data.browsingContextId;
-              break;
-            case "DOMTitleChanged":
-              this._contentTitle = data.title;
-              break;
-            case "ImageDocumentLoaded":
-              this._imageDocument = {
-                width: data.width,
-                height: data.height,
-              };
-              break;
+    // We need to get screen CSS-pixel (rather than display-pixel) coordinates.
+    // With 175% DPI, the actual ratio of display pixels to CSS pixels is
+    // 1.7647 because of rounding inside gecko. Unfortunately defaultCSSScaleFactor
+    // returns the original 1.75 dpi factor. While window.devicePixelRatio would
+    // get us the correct ratio, if the window is split between 2 screens,
+    // window.devicePixelRatio isn't guaranteed to match the screen we're
+    // autoscrolling on. So instead we do the same math as Gecko.
+    const scaleFactor = 60 / Math.round(60 / screen.defaultCSSScaleFactor);
+    let minX = left.value / scaleFactor + 0.5 * POPUP_SIZE;
+    let maxX = (left.value + width.value) / scaleFactor - 0.5 * POPUP_SIZE;
+    let minY = top.value / scaleFactor + 0.5 * POPUP_SIZE;
+    let maxY = (top.value + height.value) / scaleFactor - 0.5 * POPUP_SIZE;
+    let popupX = Math.max(minX, Math.min(maxX, screenX));
+    let popupY = Math.max(minY, Math.min(maxY, screenY));
+    this._autoScrollPopup.openPopupAtScreen(popupX, popupY);
+    this._ignoreMouseEvents = true;
+    this._scrolling = true;
+    this._startX = screenX;
+    this._startY = screenY;
 
-            case "Forms:ShowDropDown": {
-              if (!this._selectParentHelper) {
-                this._selectParentHelper =
-                  ChromeUtils.import("resource://gre/modules/SelectParentHelper.jsm", {}).SelectParentHelper;
-              }
-
-              let menulist = document.getElementById(this.getAttribute("selectmenulist"));
-              menulist.menupopup.style.direction = data.direction;
+    window.addEventListener("mousemove", this, true);
+    window.addEventListener("mousedown", this, true);
+    window.addEventListener("mouseup", this, true);
+    window.addEventListener("DOMMouseScroll", this, true);
+    window.addEventListener("contextmenu", this, true);
+    window.addEventListener("keydown", this, true);
+    window.addEventListener("keypress", this, true);
+    window.addEventListener("keyup", this, true);
+  }
 
-              let zoom = Services.prefs.getBoolPref("browser.zoom.full") ||
-                         this.isSyntheticDocument ? this._fullZoom : this._textZoom;
-              this._selectParentHelper.populate(menulist, data.options, data.selectedIndex,
-                                                zoom,
-                                                data.uaSelectBackgroundColor, data.uaSelectColor,
-                                                data.selectBackgroundColor, data.selectColor, data.selectTextShadow);
-              this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
-              break;
-            }
+  handleEvent(aEvent) {
+    if (this._scrolling) {
+      switch (aEvent.type) {
+        case "mousemove":
+          {
+            var x = aEvent.screenX - this._startX;
+            var y = aEvent.screenY - this._startY;
 
-            case "FullZoomChange": {
-              this._fullZoom = data.value;
-              let event = document.createEvent("Events");
-              event.initEvent("FullZoomChange", true, false);
-              this.dispatchEvent(event);
-              break;
+            if ((x > this._AUTOSCROLL_SNAP || x < -this._AUTOSCROLL_SNAP) ||
+              (y > this._AUTOSCROLL_SNAP || y < -this._AUTOSCROLL_SNAP))
+              this._ignoreMouseEvents = false;
+            break;
+          }
+        case "mouseup":
+        case "mousedown":
+        case "contextmenu":
+          {
+            if (!this._ignoreMouseEvents) {
+              // Use a timeout to prevent the mousedown from opening the popup again.
+              // Ideally, we could use preventDefault here, but contenteditable
+              // and middlemouse paste don't interact well. See bug 1188536.
+              setTimeout(() => this._autoScrollPopup.hidePopup(), 0);
             }
-
-            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);
+            this._ignoreMouseEvents = false;
+            break;
+          }
+        case "DOMMouseScroll":
+          {
+            this._autoScrollPopup.hidePopup();
+            aEvent.preventDefault();
+            break;
+          }
+        case "popuphidden":
+          {
+            this._autoScrollPopup.removeEventListener("popuphidden", this, true);
+            this.stopScroll();
+            break;
+          }
+        case "keydown":
+          {
+            if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+              // the escape key will be processed by
+              // nsXULPopupManager::KeyDown and the panel will be closed.
+              // So, don't consume the key event here.
               break;
             }
-
-            default:
-              return this._receiveMessage(aMessage);
-          }
-          return undefined;
-
-        ]]></body>
-      </method>
-
-      <method name="enableDisableCommandsRemoteOnly">
-        <parameter name="aAction"/>
-        <parameter name="aEnabledLength"/>
-        <parameter name="aEnabledCommands"/>
-        <parameter name="aDisabledLength"/>
-        <parameter name="aDisabledCommands"/>
-        <body>
-          <![CDATA[
-          if (this._controller) {
-            this._controller.enableDisableCommands(aAction,
-                                                   aEnabledLength, aEnabledCommands,
-                                                   aDisabledLength, aDisabledCommands);
+            // don't break here. we need to eat keydown events.
           }
-          ]]>
-        </body>
-      </method>
-
-      <method name="observe">
-        <parameter name="aSubject"/>
-        <parameter name="aTopic"/>
-        <parameter name="aState"/>
-        <body>
-          <![CDATA[
-            if (aTopic == "browser:purge-session-history") {
-              this.purgeSessionHistory();
-            } else if (aTopic == "apz:cancel-autoscroll") {
-              if (aState == this._autoScrollScrollId) {
-                // Set this._autoScrollScrollId to null, so in stopScroll() we
-                // don't call stopApzAutoscroll() (since it's APZ that
-                // initiated the stopping).
-                this._autoScrollScrollId = null;
-                this._autoScrollPresShellId = null;
-
-                this._autoScrollPopup.hidePopup();
-              }
-            }
-          ]]>
-        </body>
-      </method>
-
-      <method name="purgeSessionHistory">
-        <body>
-          <![CDATA[
-            if (this.isRemoteBrowser) {
-              try {
-                this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
-              } catch (ex) {
-                // This can throw if the browser has started to go away.
-                if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) {
-                  throw ex;
-                }
-              }
-              this._remoteWebNavigationImpl.canGoBack = false;
-              this._remoteWebNavigationImpl.canGoForward = false;
-              return;
-            }
-            this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
-          ]]>
-        </body>
-      </method>
+        case "keypress":
+        case "keyup":
+          {
+            // All keyevents should be eaten here during autoscrolling.
+            aEvent.stopPropagation();
+            aEvent.preventDefault();
+            break;
+          }
+      }
+    }
+  }
 
-      <method name="createAboutBlankContentViewer">
-        <parameter name="aPrincipal"/>
-        <body>
-          <![CDATA[
-            if (this.isRemoteBrowser) {
-              // 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);
+  closeBrowser() {
+    // The request comes from a XPCOM component, we'd want to redirect
+    // the request to tabbrowser.
+    let tabbrowser = this.getTabBrowser();
+    if (tabbrowser) {
+      let tab = tabbrowser.getTabForBrowser(this);
+      if (tab) {
+        tabbrowser.removeTab(tab);
+        return;
+      }
+    }
 
-              // Create the about blank content viewer in the content process
-              this.messageManager.sendAsyncMessage("Browser:CreateAboutBlank", aPrincipal);
-              return;
-            }
-            let principal = BrowserUtils.principalWithMatchingOA(aPrincipal, this.contentPrincipal);
-            this.docShell.createAboutBlankContentViewer(principal);
-          ]]>
-        </body>
-      </method>
-
-      <field name="_AUTOSCROLL_SNAP">10</field>
-      <field name="_scrolling">false</field>
-      <field name="_startX">null</field>
-      <field name="_startY">null</field>
-      <field name="_autoScrollPopup">null</field>
-      <field name="_autoScrollNeedsCleanup">false</field>
-      <!-- These IDs identify the scroll frame being autoscrolled. -->
-      <field name="_autoScrollScrollId">null</field>
-      <field name="_autoScrollPresShellId">null</field>
+    throw new Error("Closing a browser which was not attached to a tabbrowser is unsupported.");
+  }
 
-      <method name="stopScroll">
-        <body>
-          <![CDATA[
-            if (this._scrolling) {
-              this._scrolling = false;
-              window.removeEventListener("mousemove", this, true);
-              window.removeEventListener("mousedown", this, true);
-              window.removeEventListener("mouseup", this, true);
-              window.removeEventListener("DOMMouseScroll", this, true);
-              window.removeEventListener("contextmenu", this, true);
-              window.removeEventListener("keydown", this, true);
-              window.removeEventListener("keypress", this, true);
-              window.removeEventListener("keyup", this, true);
-              this.messageManager.sendAsyncMessage("Autoscroll:Stop");
-
-              var os = Cc["@mozilla.org/observer-service;1"]
-                         .getService(Ci.nsIObserverService);
-              try {
-                os.removeObserver(this, "apz:cancel-autoscroll");
-              } catch (ex) {
-                // It's not clear why this sometimes throws an exception
-              }
+  swapBrowsers(aOtherBrowser, aFlags) {
+    // The request comes from a XPCOM component, we'd want to redirect
+    // the request to tabbrowser so tabbrowser will be setup correctly,
+    // and it will eventually call swapDocShells.
+    let ourTabBrowser = this.getTabBrowser();
+    let otherTabBrowser = aOtherBrowser.getTabBrowser();
+    if (ourTabBrowser && otherTabBrowser) {
+      let ourTab = ourTabBrowser.getTabForBrowser(this);
+      let otherTab = otherTabBrowser.getTabForBrowser(aOtherBrowser);
+      ourTabBrowser.swapBrowsers(ourTab, otherTab, aFlags);
+      return;
+    }
 
-              if (this.isRemoteBrowser && this._autoScrollScrollId != null) {
-                let { tabParent } = this.frameLoader;
-                if (tabParent) {
-                  tabParent.stopApzAutoscroll(this._autoScrollScrollId,
-                                              this._autoScrollPresShellId);
-                }
-                this._autoScrollScrollId = null;
-                this._autoScrollPresShellId = null;
-              }
-            }
-         ]]>
-       </body>
-     </method>
+    // One of us is not connected to a tabbrowser, so just swap.
+    this.swapDocShells(aOtherBrowser);
+  }
 
-      <method name="_createAutoScrollPopup">
-        <body>
-          <![CDATA[
-            var popup = document.createXULElement("panel");
-            popup.className = "autoscroller";
-            // We set this attribute on the element so that mousemove
-            // events can be handled by browser-content.js.
-            popup.setAttribute("mousethrough", "always");
-            popup.setAttribute("consumeoutsideclicks", "true");
-            popup.setAttribute("rolluponmousewheel", "true");
-            popup.setAttribute("hidden", "true");
-            return popup;
-          ]]>
-        </body>
-      </method>
+  swapDocShells(aOtherBrowser) {
+    if (this.isRemoteBrowser != aOtherBrowser.isRemoteBrowser)
+      throw new Error("Can only swap docshells between browsers in the same process.");
 
-      <method name="startScroll">
-        <parameter name="scrolldir"/>
-        <parameter name="screenX"/>
-        <parameter name="screenY"/>
-        <body><![CDATA[
-            const POPUP_SIZE = 32;
-            if (!this._autoScrollPopup) {
-              if (this.hasAttribute("autoscrollpopup")) {
-                // our creator provided a popup to share
-                this._autoScrollPopup = document.getElementById(this.getAttribute("autoscrollpopup"));
-              } else {
-                // we weren't provided a popup; we have to use the global scope
-                this._autoScrollPopup = this._createAutoScrollPopup();
-                document.documentElement.appendChild(this._autoScrollPopup);
-                this._autoScrollNeedsCleanup = true;
-              }
-              this._autoScrollPopup.removeAttribute("hidden");
-              this._autoScrollPopup.setAttribute("noautofocus", "true");
-              this._autoScrollPopup.style.height = POPUP_SIZE + "px";
-              this._autoScrollPopup.style.width = POPUP_SIZE + "px";
-              this._autoScrollPopup.style.margin = -POPUP_SIZE / 2 + "px";
-            }
-
-            let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"]
-              .getService(Ci.nsIScreenManager);
-            let screen = screenManager.screenForRect(screenX, screenY, 1, 1);
-
-            // we need these attributes so themers don't need to create per-platform packages
-            if (screen.colorDepth > 8) { // need high color for transparency
-              // Exclude second-rate platforms
-              this._autoScrollPopup.setAttribute("transparent", !/BeOS|OS\/2/.test(navigator.appVersion));
-              // Enable translucency on Windows and Mac
-              this._autoScrollPopup.setAttribute("translucent", /Win|Mac/.test(navigator.platform));
-            }
-
-            this._autoScrollPopup.setAttribute("scrolldir", scrolldir);
-            this._autoScrollPopup.addEventListener("popuphidden", this, true);
-
-            // Sanitize screenX/screenY for available screen size with half the size
-            // of the popup removed. The popup uses negative margins to center on the
-            // coordinates we pass. Unfortunately `window.screen.availLeft` can be negative
-            // on Windows in multi-monitor setups, so we use nsIScreenManager instead:
-            let left = {}, top = {}, width = {}, height = {};
-            screen.GetAvailRect(left, top, width, height);
-
-            // We need to get screen CSS-pixel (rather than display-pixel) coordinates.
-            // With 175% DPI, the actual ratio of display pixels to CSS pixels is
-            // 1.7647 because of rounding inside gecko. Unfortunately defaultCSSScaleFactor
-            // returns the original 1.75 dpi factor. While window.devicePixelRatio would
-            // get us the correct ratio, if the window is split between 2 screens,
-            // window.devicePixelRatio isn't guaranteed to match the screen we're
-            // autoscrolling on. So instead we do the same math as Gecko.
-            const scaleFactor = 60 / Math.round(60 / screen.defaultCSSScaleFactor);
-            let minX = left.value / scaleFactor + 0.5 * POPUP_SIZE;
-            let maxX = (left.value + width.value) / scaleFactor - 0.5 * POPUP_SIZE;
-            let minY = top.value / scaleFactor + 0.5 * POPUP_SIZE;
-            let maxY = (top.value + height.value) / scaleFactor - 0.5 * POPUP_SIZE;
-            let popupX = Math.max(minX, Math.min(maxX, screenX));
-            let popupY = Math.max(minY, Math.min(maxY, screenY));
-            this._autoScrollPopup.openPopupAtScreen(popupX, popupY);
-            this._ignoreMouseEvents = true;
-            this._scrolling = true;
-            this._startX = screenX;
-            this._startY = screenY;
-
-            window.addEventListener("mousemove", this, true);
-            window.addEventListener("mousedown", this, true);
-            window.addEventListener("mouseup", this, true);
-            window.addEventListener("DOMMouseScroll", this, true);
-            window.addEventListener("contextmenu", this, true);
-            window.addEventListener("keydown", this, true);
-            window.addEventListener("keypress", this, true);
-            window.addEventListener("keyup", this, true);
-         ]]>
-        </body>
-      </method>
-
-      <method name="handleEvent">
-        <parameter name="aEvent"/>
-        <body>
-        <![CDATA[
-          if (this._scrolling) {
-            switch (aEvent.type) {
-              case "mousemove": {
-                var x = aEvent.screenX - this._startX;
-                var y = aEvent.screenY - this._startY;
+    // Give others a chance to swap state.
+    // IMPORTANT: Since a swapDocShells call does not swap the messageManager
+    //            instances attached to a browser to aOtherBrowser, others
+    //            will need to add the message listeners to the new
+    //            messageManager.
+    //            This is not a bug in swapDocShells or the FrameLoader,
+    //            merely a design decision: If message managers were swapped,
+    //            so that no new listeners were needed, the new
+    //            aOtherBrowser.messageManager would have listeners pointing
+    //            to the JS global of the current browser, which would rather
+    //            easily create leaks while swapping.
+    // IMPORTANT2: When the current browser element is removed from DOM,
+    //             which is quite common after a swpDocShells call, its
+    //             frame loader is destroyed, and that destroys the relevant
+    //             message manager, which will remove the listeners.
+    let event = new CustomEvent("SwapDocShells", { "detail": aOtherBrowser });
+    this.dispatchEvent(event);
+    event = new CustomEvent("SwapDocShells", { "detail": this });
+    aOtherBrowser.dispatchEvent(event);
 
-                if ((x > this._AUTOSCROLL_SNAP || x < -this._AUTOSCROLL_SNAP) ||
-                    (y > this._AUTOSCROLL_SNAP || y < -this._AUTOSCROLL_SNAP))
-                  this._ignoreMouseEvents = false;
-                break;
-              }
-              case "mouseup":
-              case "mousedown":
-              case "contextmenu": {
-                if (!this._ignoreMouseEvents) {
-                  // Use a timeout to prevent the mousedown from opening the popup again.
-                  // Ideally, we could use preventDefault here, but contenteditable
-                  // and middlemouse paste don't interact well. See bug 1188536.
-                  setTimeout(() => this._autoScrollPopup.hidePopup(), 0);
-                }
-                this._ignoreMouseEvents = false;
-                break;
-              }
-              case "DOMMouseScroll": {
-                this._autoScrollPopup.hidePopup();
-                aEvent.preventDefault();
-                break;
-              }
-              case "popuphidden": {
-                this._autoScrollPopup.removeEventListener("popuphidden", this, true);
-                this.stopScroll();
-                break;
-              }
-              case "keydown": {
-                if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
-                  // the escape key will be processed by
-                  // nsXULPopupManager::KeyDown and the panel will be closed.
-                  // So, don't consume the key event here.
-                  break;
-                }
-                // don't break here. we need to eat keydown events.
-              }
-              case "keypress":
-              case "keyup": {
-                // All keyevents should be eaten here during autoscrolling.
-                aEvent.stopPropagation();
-                aEvent.preventDefault();
-                break;
-              }
-            }
-          }
-        ]]>
-        </body>
-      </method>
+    // We need to swap fields that are tied to our docshell or related to
+    // the loaded page
+    // Fields which are built as a result of notifactions (pageshow/hide,
+    // DOMLinkAdded/Removed, onStateChange) should not be swapped here,
+    // because these notifications are dispatched again once the docshells
+    // are swapped.
+    var fieldsToSwap = [
+      "_webBrowserFind",
+    ];
 
-      <method name="closeBrowser">
-        <body>
-        <![CDATA[
-          // The request comes from a XPCOM component, we'd want to redirect
-          // the request to tabbrowser.
-          let tabbrowser = this.getTabBrowser();
-          if (tabbrowser) {
-            let tab = tabbrowser.getTabForBrowser(this);
-            if (tab) {
-              tabbrowser.removeTab(tab);
-              return;
-            }
-          }
-
-          throw new Error("Closing a browser which was not attached to a tabbrowser is unsupported.");
-        ]]>
-        </body>
-      </method>
+    if (this.isRemoteBrowser) {
+      fieldsToSwap.push(...[
+        "_remoteWebNavigation",
+        "_remoteWebNavigationImpl",
+        "_remoteWebProgressManager",
+        "_remoteWebProgress",
+        "_remoteFinder",
+        "_securityUI",
+        "_documentURI",
+        "_documentContentType",
+        "_contentTitle",
+        "_characterSet",
+        "_mayEnableCharacterEncodingMenu",
+        "_contentPrincipal",
+        "_imageDocument",
+        "_fullZoom",
+        "_textZoom",
+        "_isSyntheticDocument",
+        "_innerWindowID",
+      ]);
+    }
 
-      <method name="swapBrowsers">
-        <parameter name="aOtherBrowser"/>
-        <parameter name="aFlags"/>
-        <body>
-        <![CDATA[
-          // The request comes from a XPCOM component, we'd want to redirect
-          // the request to tabbrowser so tabbrowser will be setup correctly,
-          // and it will eventually call swapDocShells.
-          let ourTabBrowser = this.getTabBrowser();
-          let otherTabBrowser = aOtherBrowser.getTabBrowser();
-          if (ourTabBrowser && otherTabBrowser) {
-            let ourTab = ourTabBrowser.getTabForBrowser(this);
-            let otherTab = otherTabBrowser.getTabForBrowser(aOtherBrowser);
-            ourTabBrowser.swapBrowsers(ourTab, otherTab, aFlags);
-            return;
-          }
-
-          // One of us is not connected to a tabbrowser, so just swap.
-          this.swapDocShells(aOtherBrowser);
-        ]]>
-        </body>
-      </method>
-
-      <method name="swapDocShells">
-        <parameter name="aOtherBrowser"/>
-        <body>
-        <![CDATA[
-          if (this.isRemoteBrowser != aOtherBrowser.isRemoteBrowser)
-            throw new Error("Can only swap docshells between browsers in the same process.");
+    var ourFieldValues = {};
+    var otherFieldValues = {};
+    for (let field of fieldsToSwap) {
+      ourFieldValues[field] = this[field];
+      otherFieldValues[field] = aOtherBrowser[field];
+    }
 
-          // Give others a chance to swap state.
-          // IMPORTANT: Since a swapDocShells call does not swap the messageManager
-          //            instances attached to a browser to aOtherBrowser, others
-          //            will need to add the message listeners to the new
-          //            messageManager.
-          //            This is not a bug in swapDocShells or the FrameLoader,
-          //            merely a design decision: If message managers were swapped,
-          //            so that no new listeners were needed, the new
-          //            aOtherBrowser.messageManager would have listeners pointing
-          //            to the JS global of the current browser, which would rather
-          //            easily create leaks while swapping.
-          // IMPORTANT2: When the current browser element is removed from DOM,
-          //             which is quite common after a swpDocShells call, its
-          //             frame loader is destroyed, and that destroys the relevant
-          //             message manager, which will remove the listeners.
-          let event = new CustomEvent("SwapDocShells", {"detail": aOtherBrowser});
-          this.dispatchEvent(event);
-          event = new CustomEvent("SwapDocShells", {"detail": this});
-          aOtherBrowser.dispatchEvent(event);
+    if (window.PopupNotifications)
+      PopupNotifications._swapBrowserNotifications(aOtherBrowser, this);
 
-          // We need to swap fields that are tied to our docshell or related to
-          // the loaded page
-          // Fields which are built as a result of notifactions (pageshow/hide,
-          // DOMLinkAdded/Removed, onStateChange) should not be swapped here,
-          // because these notifications are dispatched again once the docshells
-          // are swapped.
-          var fieldsToSwap = [
-            "_webBrowserFind",
-          ];
+    try {
+      this.swapFrameLoaders(aOtherBrowser);
+    } catch (ex) {
+      // This may not be implemented for browser elements that are not
+      // attached to a BrowserDOMWindow.
+    }
 
-          if (this.isRemoteBrowser) {
-            fieldsToSwap.push(...[
-              "_remoteWebNavigation",
-              "_remoteWebNavigationImpl",
-              "_remoteWebProgressManager",
-              "_remoteWebProgress",
-              "_remoteFinder",
-              "_securityUI",
-              "_documentURI",
-              "_documentContentType",
-              "_contentTitle",
-              "_characterSet",
-              "_mayEnableCharacterEncodingMenu",
-              "_contentPrincipal",
-              "_imageDocument",
-              "_fullZoom",
-              "_textZoom",
-              "_isSyntheticDocument",
-              "_innerWindowID",
-            ]);
-          }
+    for (let field of fieldsToSwap) {
+      this[field] = otherFieldValues[field];
+      aOtherBrowser[field] = ourFieldValues[field];
+    }
 
-          var ourFieldValues = {};
-          var otherFieldValues = {};
-          for (let field of fieldsToSwap) {
-            ourFieldValues[field] = this[field];
-            otherFieldValues[field] = aOtherBrowser[field];
-          }
-
-          if (window.PopupNotifications)
-            PopupNotifications._swapBrowserNotifications(aOtherBrowser, this);
-
-          try {
-            this.swapFrameLoaders(aOtherBrowser);
-          } catch (ex) {
-            // This may not be implemented for browser elements that are not
-            // attached to a BrowserDOMWindow.
-          }
-
-          for (let field of fieldsToSwap) {
-            this[field] = otherFieldValues[field];
-            aOtherBrowser[field] = ourFieldValues[field];
-          }
-
-          if (!this.isRemoteBrowser) {
-            // Null the current nsITypeAheadFind instances so that they're
-            // lazily re-created on access. We need to do this because they
-            // might have attached the wrong docShell.
-            this._fastFind = aOtherBrowser._fastFind = null;
-          } else {
-            // Rewire the remote listeners
-            this._remoteWebNavigationImpl.swapBrowser(this);
-            aOtherBrowser._remoteWebNavigationImpl.swapBrowser(aOtherBrowser);
+    if (!this.isRemoteBrowser) {
+      // Null the current nsITypeAheadFind instances so that they're
+      // lazily re-created on access. We need to do this because they
+      // might have attached the wrong docShell.
+      this._fastFind = aOtherBrowser._fastFind = null;
+    } else {
+      // Rewire the remote listeners
+      this._remoteWebNavigationImpl.swapBrowser(this);
+      aOtherBrowser._remoteWebNavigationImpl.swapBrowser(aOtherBrowser);
 
-            if (this._remoteWebProgressManager && aOtherBrowser._remoteWebProgressManager) {
-              this._remoteWebProgressManager.swapBrowser(this);
-              aOtherBrowser._remoteWebProgressManager.swapBrowser(aOtherBrowser);
-            }
-
-            if (this._remoteFinder)
-              this._remoteFinder.swapBrowser(this);
-            if (aOtherBrowser._remoteFinder)
-              aOtherBrowser._remoteFinder.swapBrowser(aOtherBrowser);
-          }
+      if (this._remoteWebProgressManager && aOtherBrowser._remoteWebProgressManager) {
+        this._remoteWebProgressManager.swapBrowser(this);
+        aOtherBrowser._remoteWebProgressManager.swapBrowser(aOtherBrowser);
+      }
 
-          event = new CustomEvent("EndSwapDocShells", {"detail": aOtherBrowser});
-          this.dispatchEvent(event);
-          event = new CustomEvent("EndSwapDocShells", {"detail": this});
-          aOtherBrowser.dispatchEvent(event);
-        ]]>
-        </body>
-      </method>
-
-      <field name="_permitUnloadId">0</field>
-      <method name="getInPermitUnload">
-        <parameter name="aCallback"/>
-        <body>
-        <![CDATA[
-          if (this.isRemoteBrowser) {
-            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);
-            });
-            return;
-          }
+      if (this._remoteFinder)
+        this._remoteFinder.swapBrowser(this);
+      if (aOtherBrowser._remoteFinder)
+        aOtherBrowser._remoteFinder.swapBrowser(aOtherBrowser);
+    }
 
-          if (!this.docShell || !this.docShell.contentViewer) {
-            aCallback(false);
-            return;
-          }
-          aCallback(this.docShell.contentViewer.inPermitUnload);
-        ]]>
-        </body>
-      </method>
-
-      <property name="dontPromptAndDontUnload"
-                onget="return 1;"
-                readonly="true"/>
-
-      <property name="dontPromptAndUnload"
-                onget="return 2;"
-                readonly="true"/>
-
-      <method name="permitUnload">
-        <parameter name="aPermitUnloadFlags"/>
-        <body>
-        <![CDATA[
-          if (this.isRemoteBrowser) {
-            let {tabParent} = this.frameLoader;
+    event = new CustomEvent("EndSwapDocShells", { "detail": aOtherBrowser });
+    this.dispatchEvent(event);
+    event = new CustomEvent("EndSwapDocShells", { "detail": this });
+    aOtherBrowser.dispatchEvent(event);
+  }
 
-            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 = ChromeUtils.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);
-              }
-            };
+  getInPermitUnload(aCallback) {
+    if (this.isRemoteBrowser) {
+      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);
+      });
+      return;
+    }
 
-            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");
+    if (!this.docShell || !this.docShell.contentViewer) {
+      aCallback(false);
+      return;
+    }
+    aCallback(this.docShell.contentViewer.inPermitUnload);
+  }
 
-            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 = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-            timer.initWithCallback(timeout, kTimeout, timer.TYPE_ONE_SHOT);
-
-            while (!finished && !timedOut) {
-              Services.tm.currentThread.processNextEvent(true);
-            }
-
-            return {permitUnload, timedOut};
-          }
+  permitUnload(aPermitUnloadFlags) {
+    if (this.isRemoteBrowser) {
+      let { tabParent } = this.frameLoader;
 
-          if (!this.docShell || !this.docShell.contentViewer) {
-            return {permitUnload: true, timedOut: false};
-          }
-          return {permitUnload: this.docShell.contentViewer.permitUnload(aPermitUnloadFlags),
-                  timedOut: false};
-        ]]>
-        </body>
-      </method>
+      if (!tabParent.hasBeforeUnload) {
+        return { permitUnload: true, timedOut: false };
+      }
 
-      <method name="print">
-        <parameter name="aOuterWindowID"/>
-        <parameter name="aPrintSettings"/>
-        <parameter name="aPrintProgressListener"/>
-        <body>
-          <![CDATA[
-            if (!this.frameLoader) {
-              throw Components.Exception("No frame loader.",
-                                         Cr.NS_ERROR_FAILURE);
-            }
+      const kTimeout = 1000;
 
-            this.frameLoader.print(aOuterWindowID, aPrintSettings,
-                                   aPrintProgressListener);
-          ]]>
-        </body>
-      </method>
-
-      <method name="drawSnapshot">
-        <parameter name="x"/>
-        <parameter name="y"/>
-        <parameter name="w"/>
-        <parameter name="h"/>
-        <parameter name="scale"/>
-        <parameter name="backgroundColor"/>
-        <body>
-          <![CDATA[
-            if (!this.frameLoader) {
-              throw Components.Exception("No frame loader.",
-                                         Cr.NS_ERROR_FAILURE);
-            }
-            return this.frameLoader.drawSnapshot(x, y, w, h, scale, backgroundColor);
-          ]]>
-        </body>
-      </method>
+      let finished = false;
+      let responded = false;
+      let permitUnload;
+      let id = this._permitUnloadId++;
+      let mm = this.messageManager;
+      let Services = ChromeUtils.import("resource://gre/modules/Services.jsm", {}).Services;
 
-      <method name="dropLinks">
-        <parameter name="aLinksCount"/>
-        <parameter name="aLinks"/>
-        <parameter name="aTriggeringPrincipal"/>
-        <body><![CDATA[
-          if (!this.droppedLinkHandler) {
-            return false;
-          }
-          let links = [];
-          for (let i = 0; i < aLinksCount; i += 3) {
-            links.push({
-              url: aLinks[i],
-              name: aLinks[i + 1],
-              type: aLinks[i + 2],
-            });
-          }
-          this.droppedLinkHandler(null, links, aTriggeringPrincipal);
-          return true;
-        ]]></body>
-      </method>
+      let msgListener = msg => {
+        if (msg.data.id != id) {
+          return;
+        }
+        if (msg.data.kind == "start") {
+          responded = true;
+          return;
+        }
+        done(msg.data.permitUnload);
+      };
 
-      <method name="getContentBlockingLog">
-        <body><![CDATA[
-          if (this.isRemoteBrowser) {
-            return this.frameLoader.tabParent.getContentBlockingLog();
-          }
-          return this.docShell ?
-                   this.docShell.getContentBlockingLog() :
-                   Promise.reject("docshell isn't available");
-        ]]></body>
-      </method>
-    </implementation>
-
-    <handlers>
-      <handler event="keypress" keycode="VK_F7" group="system">
-        <![CDATA[
-          if (event.defaultPrevented || !event.isTrusted)
-            return;
-
-          const kPrefShortcutEnabled = "accessibility.browsewithcaret_shortcut.enabled";
-          const kPrefWarnOnEnable    = "accessibility.warn_on_browsewithcaret";
-          const kPrefCaretBrowsingOn = "accessibility.browsewithcaret";
-
-          var isEnabled = this.mPrefs.getBoolPref(kPrefShortcutEnabled);
-          if (!isEnabled)
-            return;
+      let observer = subject => {
+        if (subject == mm) {
+          done(true);
+        }
+      };
 
-          // Toggle browse with caret mode
-          var browseWithCaretOn = this.mPrefs.getBoolPref(kPrefCaretBrowsingOn, false);
-          var warn = this.mPrefs.getBoolPref(kPrefWarnOnEnable, true);
-          if (warn && !browseWithCaretOn) {
-            var checkValue = {value: false};
-            var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"]
-                                  .getService(Ci.nsIPromptService);
+      function done(result) {
+        finished = true;
+        permitUnload = result;
+        mm.removeMessageListener("PermitUnload", msgListener);
+        Services.obs.removeObserver(observer, "message-manager-close");
+      }
 
-            var buttonPressed = promptService.confirmEx(window,
-              this.mStrBundle.GetStringFromName("browsewithcaret.checkWindowTitle"),
-              this.mStrBundle.GetStringFromName("browsewithcaret.checkLabel"),
-              // Make "No" the default:
-              promptService.STD_YES_NO_BUTTONS | promptService.BUTTON_POS_1_DEFAULT,
-              null, null, null, this.mStrBundle.GetStringFromName("browsewithcaret.checkMsg"),
-              checkValue);
-            if (buttonPressed != 0) {
-              if (checkValue.value) {
-                try {
-                  this.mPrefs.setBoolPref(kPrefShortcutEnabled, false);
-                } catch (ex) {
-                }
-              }
-              return;
-            }
-            if (checkValue.value) {
-              try {
-                this.mPrefs.setBoolPref(kPrefWarnOnEnable, false);
-              } catch (ex) {
-              }
-            }
-          }
+      mm.sendAsyncMessage("PermitUnload", { id, aPermitUnloadFlags });
+      mm.addMessageListener("PermitUnload", msgListener);
+      Services.obs.addObserver(observer, "message-manager-close");
 
-          // Toggle the pref
-          try {
-            this.mPrefs.setBoolPref(kPrefCaretBrowsingOn, !browseWithCaretOn);
-          } catch (ex) {
-          }
-        ]]>
-      </handler>
-      <handler event="dragover" group="system">
-      <![CDATA[
-        if (!this.droppedLinkHandler || event.defaultPrevented)
-          return;
+      let timedOut = false;
 
-        // For drags that appear to be internal text (for example, tab drags),
-        // set the dropEffect to 'none'. This prevents the drop even if some
-        // other listener cancelled the event.
-        var types = event.dataTransfer.types;
-        if (types.includes("text/x-moz-text-internal") && !types.includes("text/plain")) {
-          event.dataTransfer.dropEffect = "none";
-          event.stopPropagation();
-          event.preventDefault();
+      function timeout() {
+        if (!responded) {
+          timedOut = true;
         }
 
-        // No need to handle "dragover" in e10s, since nsDocShellTreeOwner.cpp in the child process
-        // handles that case using "@mozilla.org/content/dropped-link-handler;1" service.
-        if (this.isRemoteBrowser)
-          return;
+        // Dispatch something to ensure that the main thread wakes up.
+        Services.tm.dispatchToMainThread(function() {});
+      }
+
+      let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+      timer.initWithCallback(timeout, kTimeout, timer.TYPE_ONE_SHOT);
+
+      while (!finished && !timedOut) {
+        Services.tm.currentThread.processNextEvent(true);
+      }
+
+      return { permitUnload, timedOut };
+    }
 
-        let linkHandler = Cc["@mozilla.org/content/dropped-link-handler;1"].
-                            getService(Ci.nsIDroppedLinkHandler);
-        if (linkHandler.canDropLink(event, false))
-          event.preventDefault();
-      ]]>
-      </handler>
-      <handler event="drop" group="system">
-      <![CDATA[
-        // No need to handle "drop" in e10s, since nsDocShellTreeOwner.cpp in the child process
-        // handles that case using "@mozilla.org/content/dropped-link-handler;1" service.
-        if (!this.droppedLinkHandler || event.defaultPrevented || this.isRemoteBrowser)
-          return;
+    if (!this.docShell || !this.docShell.contentViewer) {
+      return { permitUnload: true, timedOut: false };
+    }
+    return {
+      permitUnload: this.docShell.contentViewer.permitUnload(aPermitUnloadFlags),
+      timedOut: false
+    };
+  }
+
+  print(aOuterWindowID, aPrintSettings, aPrintProgressListener) {
+    if (!this.frameLoader) {
+      throw Components.Exception("No frame loader.",
+        Cr.NS_ERROR_FAILURE);
+    }
+
+    this.frameLoader.print(aOuterWindowID, aPrintSettings,
+      aPrintProgressListener);
+  }
 
-        let linkHandler = Cc["@mozilla.org/content/dropped-link-handler;1"].
-                            getService(Ci.nsIDroppedLinkHandler);
-        try {
-          // Pass true to prevent the dropping of javascript:/data: URIs
-          var links = linkHandler.dropLinks(event, true);
-        } catch (ex) {
-          return;
-        }
+  drawSnapshot(x, y, w, h, scale, backgroundColor) {
+    if (!this.frameLoader) {
+      throw Components.Exception("No frame loader.",
+        Cr.NS_ERROR_FAILURE);
+    }
+    return this.frameLoader.drawSnapshot(x, y, w, h, scale, backgroundColor);
+  }
 
-        if (links.length) {
-          let triggeringPrincipal = linkHandler.getTriggeringPrincipal(event);
-          this.droppedLinkHandler(event, links, triggeringPrincipal);
-        }
-      ]]>
-      </handler>
-      <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.
-        if (this.isRemoteBrowser) {
-          event.stopPropagation();
-        }
-      ]]>
-      </handler>
-    </handlers>
+  dropLinks(aLinksCount, aLinks, aTriggeringPrincipal) {
+    if (!this.droppedLinkHandler) {
+      return false;
+    }
+    let links = [];
+    for (let i = 0; i < aLinksCount; i += 3) {
+      links.push({
+        url: aLinks[i],
+        name: aLinks[i + 1],
+        type: aLinks[i + 2],
+      });
+    }
+    this.droppedLinkHandler(null, links, aTriggeringPrincipal);
+    return true;
+  }
 
-  </binding>
+  getContentBlockingLog() {
+    if (this.isRemoteBrowser) {
+      return this.frameLoader.tabParent.getContentBlockingLog();
+    }
+    return this.docShell ?
+      this.docShell.getContentBlockingLog() :
+      Promise.reject("docshell isn't available");
+  }
+  disconnectedCallback() {
+    this.destroy();
+  }
+}
 
-</bindings>
+MozXULElement.implementCustomInterface(MozBrowser, [Ci.nsIObserver, Ci.nsIBrowser]);
+customElements.define("browser", MozBrowser);
+
+}