Bug 938359 - [e10s] Support middle-click scroll (r=felipe)
☠☠ backed out by 32b52e414a28 ☠ ☠
authorBill McCloskey <wmccloskey@mozilla.com>
Thu, 13 Mar 2014 15:31:03 -0700
changeset 191718 77fdb4380b1a6b8fc2781ba5bc8c80a867a7212d
parent 191717 aefd98bc5a2d24d3d514fc3d646b462b91a5998c
child 191719 d6261f65070af34b991dc4177e21321f5d5eac72
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe
bugs938359
milestone30.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 938359 - [e10s] Support middle-click scroll (r=felipe)
browser/base/content/content.js
toolkit/content/browser-content.js
toolkit/content/jar.mn
toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js
toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js
toolkit/content/widgets/browser.xml
toolkit/content/widgets/remote-browser.xml
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -3,16 +3,17 @@
  * 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/. */
 
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
   "resource:///modules/ContentLinkHandler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
   "resource://gre/modules/LoginManagerContent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
   "resource://gre/modules/InsecurePasswordUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
new file mode 100644
--- /dev/null
+++ b/toolkit/content/browser-content.js
@@ -0,0 +1,234 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+var global = this;
+
+let ClickEventHandler = {
+  init: function init() {
+    this._scrollable = null;
+    this._scrolldir = "";
+    this._startX = null;
+    this._startY = null;
+    this._screenX = null;
+    this._screenY = null;
+    this._lastFrame = null;
+
+    Cc["@mozilla.org/eventlistenerservice;1"]
+      .getService(Ci.nsIEventListenerService)
+      .addSystemEventListener(global, "mousedown", this, true);
+
+    addMessageListener("Autoscroll:Stop", this);
+  },
+
+  isAutoscrollBlocker: function(node) {
+    let mmPaste = Services.prefs.getBoolPref("middlemouse.paste");
+    let mmScrollbarPosition = Services.prefs.getBoolPref("middlemouse.scrollbarPosition");
+
+    while (node) {
+      if ((node instanceof content.HTMLAnchorElement || node instanceof content.HTMLAreaElement) &&
+          node.hasAttribute("href")) {
+        return true;
+      }
+
+      if (mmPaste && (node instanceof content.HTMLInputElement ||
+                      node instanceof content.HTMLTextAreaElement)) {
+        return true;
+      }
+
+      if (node instanceof content.XULElement && mmScrollbarPosition
+          && (node.localName == "scrollbar" || node.localName == "scrollcorner")) {
+        return true;
+      }
+
+      node = node.parentNode;
+    }
+    return false;
+  },
+
+  startScroll: function(event) {
+    // this is a list of overflow property values that allow scrolling
+    const scrollingAllowed = ['scroll', 'auto'];
+
+    // go upward in the DOM and find any parent element that has a overflow
+    // area and can therefore be scrolled
+    for (this._scrollable = event.originalTarget; this._scrollable;
+         this._scrollable = this._scrollable.parentNode) {
+      // do not use overflow based autoscroll for <html> and <body>
+      // Elements or non-html elements such as svg or Document nodes
+      // also make sure to skip select elements that are not multiline
+      if (!(this._scrollable instanceof content.HTMLElement) ||
+          ((this._scrollable instanceof content.HTMLSelectElement) && !this._scrollable.multiple)) {
+        continue;
+      }
+
+      var overflowx = this._scrollable.ownerDocument.defaultView
+                          .getComputedStyle(this._scrollable, '')
+                          .getPropertyValue('overflow-x');
+      var overflowy = this._scrollable.ownerDocument.defaultView
+                          .getComputedStyle(this._scrollable, '')
+                          .getPropertyValue('overflow-y');
+      // we already discarded non-multiline selects so allow vertical
+      // scroll for multiline ones directly without checking for a
+      // overflow property
+      var scrollVert = this._scrollable.scrollTopMax &&
+        (this._scrollable instanceof content.HTMLSelectElement ||
+         scrollingAllowed.indexOf(overflowy) >= 0);
+
+      // do not allow horizontal scrolling for select elements, it leads
+      // to visual artifacts and is not the expected behavior anyway
+      if (!(this._scrollable instanceof content.HTMLSelectElement) &&
+          this._scrollable.scrollLeftMax &&
+          scrollingAllowed.indexOf(overflowx) >= 0) {
+        this._scrolldir = scrollVert ? "NSEW" : "EW";
+        break;
+      } else if (scrollVert) {
+        this._scrolldir = "NS";
+        break;
+      }
+    }
+
+    if (!this._scrollable) {
+      this._scrollable = event.originalTarget.ownerDocument.defaultView;
+      if (this._scrollable.scrollMaxX > 0) {
+        this._scrolldir = this._scrollable.scrollMaxY > 0 ? "NSEW" : "EW";
+      } else if (this._scrollable.scrollMaxY > 0) {
+        this._scrolldir = "NS";
+      } else {
+        this._scrollable = null; // abort scrolling
+        return;
+      }
+    }
+
+    Cc["@mozilla.org/eventlistenerservice;1"]
+      .getService(Ci.nsIEventListenerService)
+      .addSystemEventListener(global, "mousemove", this, true);
+    addEventListener("pagehide", this, true);
+
+    sendAsyncMessage("Autoscroll:Start", {scrolldir: this._scrolldir,
+                                          screenX: event.screenX,
+                                          screenY: event.screenY});
+    this._ignoreMouseEvents = true;
+    this._startX = event.screenX;
+    this._startY = event.screenY;
+    this._screenX = event.screenX;
+    this._screenY = event.screenY;
+    this._scrollErrorX = 0;
+    this._scrollErrorY = 0;
+    this._lastFrame = content.mozAnimationStartTime;
+
+    content.mozRequestAnimationFrame(this);
+  },
+
+  stopScroll: function() {
+    if (this._scrollable) {
+      this._scrollable = null;
+
+      Cc["@mozilla.org/eventlistenerservice;1"]
+        .getService(Ci.nsIEventListenerService)
+        .removeSystemEventListener(global, "mousemove", this, true);
+      removeEventListener("pagehide", this, true);
+    }
+  },
+
+  accelerate: function(curr, start) {
+    const speed = 12;
+    var val = (curr - start) / speed;
+
+    if (val > 1)
+      return val * Math.sqrt(val) - 1;
+    if (val < -1)
+      return val * Math.sqrt(-val) + 1;
+    return 0;
+  },
+
+  roundToZero: function(num) {
+    if (num > 0)
+      return Math.floor(num);
+    return Math.ceil(num);
+  },
+
+  autoscrollLoop: function(timestamp) {
+    if (!this._scrollable) {
+      // Scrolling has been canceled
+      return;
+    }
+
+    // avoid long jumps when the browser hangs for more than
+    // |maxTimeDelta| ms
+    const maxTimeDelta = 100;
+    var timeDelta = Math.min(maxTimeDelta, timestamp - this._lastFrame);
+    // we used to scroll |accelerate()| pixels every 20ms (50fps)
+    var timeCompensation = timeDelta / 20;
+    this._lastFrame = timestamp;
+
+    var actualScrollX = 0;
+    var actualScrollY = 0;
+    // don't bother scrolling vertically when the scrolldir is only horizontal
+    // and the other way around
+    if (this._scrolldir != 'EW') {
+      var y = this.accelerate(this._screenY, this._startY) * timeCompensation;
+      var desiredScrollY = this._scrollErrorY + y;
+      actualScrollY = this.roundToZero(desiredScrollY);
+      this._scrollErrorY = (desiredScrollY - actualScrollY);
+    }
+    if (this._scrolldir != 'NS') {
+      var x = this.accelerate(this._screenX, this._startX) * timeCompensation;
+      var desiredScrollX = this._scrollErrorX + x;
+      actualScrollX = this.roundToZero(desiredScrollX);
+      this._scrollErrorX = (desiredScrollX - actualScrollX);
+    }
+
+    if (this._scrollable instanceof content.Window) {
+      this._scrollable.scrollBy(actualScrollX, actualScrollY);
+    } else { // an element with overflow
+      this._scrollable.scrollLeft += actualScrollX;
+      this._scrollable.scrollTop += actualScrollY;
+    }
+    content.mozRequestAnimationFrame(this);
+  },
+
+  sample: function(timestamp) {
+    this.autoscrollLoop(timestamp);
+  },
+
+  handleEvent: function(event) {
+    if (event.type == "mousemove") {
+      this._screenX = event.screenX;
+      this._screenY = event.screenY;
+    } else if (event.type == "mousedown") {
+      if (event.isTrusted &
+          !event.defaultPrevented &&
+          event.button == 1 &&
+          !this._scrollable &&
+          !this.isAutoscrollBlocker(event.originalTarget)) {
+        this.startScroll(event);
+      }
+    } else if (event.type == "pagehide") {
+      if (this._scrollable) {
+        var doc =
+          this._scrollable.ownerDocument || this._scrollable.document;
+        if (doc == event.target) {
+          sendAsyncMessage("Autoscroll:Cancel");
+        }
+      }
+    }
+  },
+
+  receiveMessage: function(msg) {
+    switch (msg.name) {
+      case "Autoscroll:Stop": {
+        this.stopScroll();
+        break;
+      }
+    }
+  },
+};
+ClickEventHandler.init();
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -23,16 +23,17 @@ toolkit.jar:
 *  content/global/aboutSupport.xhtml
 *  content/global/aboutTelemetry.js
    content/global/aboutTelemetry.xhtml
    content/global/aboutTelemetry.css          (aboutTelemetry.css)
    content/global/directionDetector.html
    content/global/plugins.html
    content/global/plugins.css
    content/global/browser-child.js            (browser-child.js)
+   content/global/browser-content.js          (browser-content.js)
 *+  content/global/buildconfig.html            (buildconfig.html)
 +  content/global/charsetOverlay.js           (charsetOverlay.js)
 +  content/global/charsetOverlay.xul          (charsetOverlay.xul)
 *  content/global/contentAreaUtils.js         (contentAreaUtils.js)
    content/global/customizeCharset.js         (customizeCharset.js)
    content/global/customizeCharset.xul        (customizeCharset.xul)
    content/global/customizeToolbar.css        (customizeToolbar.css)
 *  content/global/customizeToolbar.js         (customizeToolbar.js)
--- a/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js
+++ b/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js
@@ -70,17 +70,20 @@ function test()
       var scrollVert = test.expected & expectScrollVert;
       ok((scrollVert && elem.scrollTop > 0) ||
          (!scrollVert && elem.scrollTop == 0),
          test.elem+' should'+(scrollVert ? '' : ' not')+' have scrolled vertically');
       var scrollHori = test.expected & expectScrollHori;
       ok((scrollHori && elem.scrollLeft > 0) ||
          (!scrollHori && elem.scrollLeft == 0),
          test.elem+' should'+(scrollHori ? '' : ' not')+' have scrolled horizontally');
-      nextTest();
+
+      // Before continuing the test, we need to ensure that the IPC
+      // message that stops autoscrolling has had time to arrive.
+      executeSoon(nextTest);
     };
     EventUtils.synthesizeMouse(elem, 50, 50, { button: 1 },
                                gBrowser.contentWindow);
 
     var iframe = gBrowser.contentDocument.getElementById("iframe");
     var e = iframe.contentDocument.createEvent("pagetransition");
     e.initPageTransitionEvent("pagehide", true, true, false);
     iframe.contentDocument.dispatchEvent(e);
--- a/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js
+++ b/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js
@@ -85,16 +85,22 @@ function test()
     // Test whether the key events are handled correctly under normal condition
     expectedKeyEvents = kAllKeyEvents;
     sendChar("A");
 
     // Start autoscrolling by middle button lick on the page
     EventUtils.synthesizeMouse(root, 10, 10, { button: 1 },
                                gBrowser.contentWindow);
 
+    // Before continuing the test, we need to ensure that the IPC
+    // message that starts autoscrolling has had time to arrive.
+    executeSoon(continueTest);
+  }
+
+  function continueTest() {
     // Most key events should be eaten by the browser.
     expectedKeyEvents = kNoKeyEvents;
     sendChar("A");
     sendKey("DOWN");
     sendKey("RETURN");
     sendKey("RETURN");
     sendKey("HOME");
     sendKey("END");
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -12,17 +12,17 @@
 <bindings id="browserBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <binding id="browser" extends="xul:browser" role="outerdoc">
     <content clickthrough="never">
       <children/>
     </content>
-    <implementation type="application/javascript" implements="nsIObserver, nsIDOMEventListener, nsIFrameRequestCallback">
+    <implementation type="application/javascript" implements="nsIObserver, nsIDOMEventListener, nsIFrameRequestCallback, nsIMessageListener">
       <property name="autoscrollEnabled">
         <getter>
           <![CDATA[
             if (this.getAttribute("autoscroll") == "false")
               return false;
 
             var enabled = true;
             try {
@@ -595,24 +595,16 @@
             if (this.feeds && aEvent.target == this.contentDocument)
               this.feeds = null;
             if (!this.docShell || !this.fastFind)
               return;
             var tabBrowser = this.getTabBrowser();
             if (!tabBrowser || !("fastFind" in tabBrowser) ||
                 tabBrowser.selectedBrowser == this)
               this.fastFind.setDocShell(this.docShell);
-
-            if (this._scrollable) {
-              var doc =
-                this._scrollable.ownerDocument || this._scrollable.document;
-              if (doc == aEvent.target) {
-                this._autoScrollPopup.hidePopup();
-              }
-            }
          ]]>
         </body>
       </method>
 
       <method name="updatePageReport">
         <body>
           <![CDATA[
             var event = document.createEvent("Events");
@@ -788,16 +780,22 @@
           }
           catch (e) {
           }
 
           // Listen for first load for lazy attachment to form fill controller
           this.addEventListener("pageshow", this.onPageShow, true);
           this.addEventListener("pagehide", this.onPageHide, true);
           this.addEventListener("DOMPopupBlocked", this.onPopupBlocked, true);
+
+          if (this.messageManager) {
+            this.messageManager.addMessageListener("Autoscroll:Start", this);
+            this.messageManager.addMessageListener("Autoscroll:Cancel", this);
+            this.messageManager.loadFrameScript("chrome://global/content/browser-content.js", true);
+          }
         ]]>
       </constructor>
 
       <destructor>
         <![CDATA[
           this.destroy();
         ]]>
       </destructor>
@@ -838,16 +836,44 @@
           if (this._autoScrollNeedsCleanup) {
             // we polluted the global scope, so clean it up
             this._autoScrollPopup.parentNode.removeChild(this._autoScrollPopup);
           }
           ]]>
         </body>
       </method>
 
+      <!--
+        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 "Autoscroll:Start": {
+              let pos = this.mapScreenCoordinatesFromContent(data.screenX, data.screenY);
+              this.startScroll(data.scrolldir, pos.x, pos.y);
+              break;
+            }
+            case "Autoscroll:Cancel":
+              this._autoScrollPopup.hidePopup();
+              break;
+          }
+        ]]></body>
+      </method>
+
+      <method name="receiveMessage">
+        <parameter name="aMessage"/>
+        <body><![CDATA[
+          return this._receiveMessage(aMessage);
+        ]]></body>
+      </method>
+
       <method name="observe">
         <parameter name="aSubject"/>
         <parameter name="aTopic"/>
         <parameter name="aState"/>
         <body>
           <![CDATA[
             if (aTopic != "browser:purge-session-history" || !this.sessionHistory)
               return;
@@ -866,37 +892,35 @@
 
             if (purge > 0)
               this.sessionHistory.PurgeHistory(purge);
           ]]>
         </body>
       </method>
 
       <field name="_AUTOSCROLL_SNAP">10</field>
-      <field name="_scrollable">null</field>
+      <field name="_scrolling">false</field>
       <field name="_startX">null</field>
       <field name="_startY">null</field>
-      <field name="_screenX">null</field>
-      <field name="_screenY">null</field>
-      <field name="_lastFrame">null</field>
       <field name="_autoScrollPopup">null</field>
       <field name="_autoScrollNeedsCleanup">false</field>
 
       <method name="stopScroll">
         <body>
           <![CDATA[
-            if (this._scrollable) {
-              this._scrollable = null;
+            if (this._scrolling) {
+              this._scrolling = false;
               window.removeEventListener("mousemove", this, true);
               window.removeEventListener("mousedown", this, true);
               window.removeEventListener("mouseup", 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");
             }
          ]]>
        </body>
      </method>
 
       <method name="_createAutoScrollPopup">
         <body>
           <![CDATA[
@@ -904,19 +928,20 @@
             var popup = document.createElementNS(XUL_NS, "panel");
             popup.className = "autoscroller";
             return popup;
           ]]>
         </body>
       </method>
 
       <method name="startScroll">
-        <parameter name="event"/>
-        <body>
-          <![CDATA[
+        <parameter name="scrolldir"/>
+        <parameter name="screenX"/>
+        <parameter name="screenY"/>
+        <body><![CDATA[
             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();
@@ -930,230 +955,45 @@
             // 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 is a list of overflow property values that allow scrolling
-            const scrollingAllowed = ['scroll', 'auto'];
-
-            // go upward in the DOM and find any parent element that has a overflow
-            // area and can therefore be scrolled
-            for (this._scrollable = event.originalTarget; this._scrollable;
-                 this._scrollable = this._scrollable.parentNode) {
-              // do not use overflow based autoscroll for <html> and <body>
-              // Elements or non-html elements such as svg or Document nodes
-              // also make sure to skip select elements that are not multiline
-              if (!(this._scrollable instanceof HTMLElement) ||
-                  ((this._scrollable instanceof HTMLSelectElement) && !this._scrollable.multiple)) {
-                continue;
-              }
-
-              var overflowx = this._scrollable.ownerDocument.defaultView
-                                  .getComputedStyle(this._scrollable, '')
-                                  .getPropertyValue('overflow-x');
-              var overflowy = this._scrollable.ownerDocument.defaultView
-                                  .getComputedStyle(this._scrollable, '')
-                                  .getPropertyValue('overflow-y');
-              // we already discarded non-multiline selects so allow vertical
-              // scroll for multiline ones directly without checking for a
-              // overflow property
-              var scrollVert = this._scrollable.scrollTopMax &&
-                               (this._scrollable instanceof HTMLSelectElement ||
-                                scrollingAllowed.indexOf(overflowy) >= 0);
-
-              // do not allow horizontal scrolling for select elements, it leads
-              // to visual artifacts and is not the expected behavior anyway
-              if (!(this._scrollable instanceof HTMLSelectElement) &&
-                  this._scrollable.scrollLeftMax &&
-                  scrollingAllowed.indexOf(overflowx) >= 0) {
-                this._autoScrollPopup.setAttribute("scrolldir", scrollVert ? "NSEW" : "EW");
-                break;
-              }
-              else if (scrollVert) {
-                this._autoScrollPopup.setAttribute("scrolldir", "NS");
-                break;
-              }
-            }
-
-            if (!this._scrollable) {
-              this._scrollable = event.originalTarget.ownerDocument.defaultView;
-              if (this._scrollable.scrollMaxX > 0) {
-                this._autoScrollPopup.setAttribute("scrolldir", this._scrollable.scrollMaxY > 0 ? "NSEW" : "EW");
-              }
-              else if (this._scrollable.scrollMaxY > 0) {
-                this._autoScrollPopup.setAttribute("scrolldir", "NS");
-              }
-              else {
-                this._scrollable = null; // abort scrolling
-                return;
-              }
-            }
-
+            this._autoScrollPopup.setAttribute("scrolldir", scrolldir);
             this._autoScrollPopup.showPopup(document.documentElement,
-                                            event.screenX,
-                                            event.screenY,
+                                            screenX,
+                                            screenY,
                                             "popup", null, null);
             this._ignoreMouseEvents = true;
-            this._startX = event.screenX;
-            this._startY = event.screenY;
-            this._screenX = event.screenX;
-            this._screenY = event.screenY;
-            this._scrollErrorX = 0;
-            this._scrollErrorY = 0;
-            this._lastFrame = window.mozAnimationStartTime;
+            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("contextmenu", this, true);
             window.addEventListener("keydown", this, true);
             window.addEventListener("keypress", this, true);
             window.addEventListener("keyup", this, true);
-
-            window.mozRequestAnimationFrame(this);
-         ]]>
-       </body>
-     </method>
-
-     <method name="_roundToZero">
-       <parameter name="num"/>
-       <body>
-         <![CDATA[
-            if (num > 0)
-              return Math.floor(num);
-            return Math.ceil(num);
-         ]]>
-       </body>
-     </method>
-
-     <method name="_accelerate">
-       <parameter name="curr"/>
-       <parameter name="start"/>
-       <body>
-         <![CDATA[
-            const speed = 12;
-            var val = (curr - start) / speed;
-
-            if (val > 1)
-              return val * Math.sqrt(val) - 1;
-            if (val < -1)
-              return val * Math.sqrt(-val) + 1;
-            return 0;
-         ]]>
-       </body>
-     </method>
-
-     <method name="autoScrollLoop">
-       <parameter name="timestamp"/>
-       <body>
-         <![CDATA[
-           if (!this._scrollable) {
-             // Scrolling has been canceled
-             return;
-           }
-
-           // avoid long jumps when the browser hangs for more than
-           // |maxTimeDelta| ms
-           const maxTimeDelta = 100;
-           var timeDelta = Math.min(maxTimeDelta, timestamp - this._lastFrame);
-           // we used to scroll |_accelerate()| pixels every 20ms (50fps)
-           var timeCompensation = timeDelta / 20;
-           this._lastFrame = timestamp;
-
-           var actualScrollX = 0;
-           var actualScrollY = 0;
-           // don't bother scrolling vertically when the scrolldir is only horizontal
-           // and the other way around
-           var scrolldir = this._autoScrollPopup.getAttribute("scrolldir");
-           if (scrolldir != 'EW') {
-             var y = this._accelerate(this._screenY, this._startY) * timeCompensation;
-             var desiredScrollY = this._scrollErrorY + y;
-             actualScrollY = this._roundToZero(desiredScrollY);
-             this._scrollErrorY = (desiredScrollY - actualScrollY);
-           }
-           if (scrolldir != 'NS') {
-             var x = this._accelerate(this._screenX, this._startX) * timeCompensation;
-             var desiredScrollX = this._scrollErrorX + x;
-             actualScrollX = this._roundToZero(desiredScrollX);
-             this._scrollErrorX = (desiredScrollX - actualScrollX);
-           }
-
-           if (this._scrollable instanceof Window)
-             this._scrollable.scrollBy(actualScrollX, actualScrollY);
-           else { // an element with overflow
-             this._scrollable.scrollLeft += actualScrollX;
-             this._scrollable.scrollTop += actualScrollY;
-           }
-           window.mozRequestAnimationFrame(this);
-         ]]>
-       </body>
-     </method>
-     <method name="isAutoscrollBlocker">
-       <parameter name="node"/>
-       <body>
-         <![CDATA[
-           var mmPaste = false;
-           var mmScrollbarPosition = false;
-
-           try {
-             mmPaste = this.mPrefs.getBoolPref("middlemouse.paste");
-           }
-           catch (ex) {
-           }
-
-           try {
-             mmScrollbarPosition = this.mPrefs.getBoolPref("middlemouse.scrollbarPosition");
-           }
-           catch (ex) {
-           }
-
-           while (node) {
-             if ((node instanceof HTMLAnchorElement || node instanceof HTMLAreaElement) && node.hasAttribute("href"))
-               return true;
-
-             if (mmPaste && (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement))
-               return true;
-
-             if (node instanceof XULElement && mmScrollbarPosition
-                 && (node.localName == "scrollbar" || node.localName == "scrollcorner"))
-               return true;
-
-             node = node.parentNode;
-           }
-           return false;
-         ]]>
-       </body>
-     </method>
-
-      <!-- nsIFrameRequestCallback implementation -->
-      <method name="sample">
-        <parameter name="timeStamp"/>
-        <body>
-          <![CDATA[
-            this.autoScrollLoop(timeStamp);
-          ]]>
-        </body>
+        ]]></body>
       </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body>
         <![CDATA[
-          if (this._scrollable) {
+          if (this._scrolling) {
             switch(aEvent.type) {
               case "mousemove": {
-                this._screenX = aEvent.screenX;
-                this._screenY = aEvent.screenY;
-
-                var x = this._screenX - this._startX;
-                var y = this._screenY - this._startY;
+                var x = aEvent.screenX - this._startX;
+                var y = aEvent.screenY - this._startY;
 
                 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":
@@ -1310,27 +1150,16 @@
 
           // Toggle the pref
           try {
             this.mPrefs.setBoolPref("accessibility.browsewithcaret",!browseWithCaretOn);
           } catch (ex) {
           }
         ]]>
       </handler>
-      <handler event="mousedown" phase="capturing">
-        <![CDATA[
-          if (!this._scrollable && event.button == 1) {
-            if (!this.autoscrollEnabled ||
-                this.isAutoscrollBlocker(event.originalTarget))
-              return;
-
-            this.startScroll(event);
-          }
-        ]]>
-      </handler>
       <handler event="dragover" group="system">
       <![CDATA[
         if (!this.droppedLinkHandler || event.defaultPrevented)
           return;
 
         // 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.
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -5,17 +5,17 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <bindings id="firefoxBrowserBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <binding id="remote-browser" extends="chrome://global/content/bindings/browser.xml#browser">
 
-    <implementation type="application/javascript" implements="nsIObserver, nsIDOMEventListener, nsIMessageListener, nsIMessageListener">
+    <implementation type="application/javascript" implements="nsIObserver, nsIDOMEventListener, nsIMessageListener">
 
       <field name="_securityUI">null</field>
 
       <property name="securityUI"
                 readonly="true">
         <getter><![CDATA[
           if (!this._securityUI) {
             let jsm = "resource://gre/modules/RemoteSecurityUI.jsm";
@@ -260,17 +260,23 @@
             case "TextZoomChange":
               this._textZoom = data.value;
               break;
 
             case "Forms:HideDropDown": {
               Cu.import("resource://gre/modules/SelectParentHelper.jsm");
               let dropdown = document.getElementById(this.getAttribute("selectpopup"));
               SelectParentHelper.hide(dropdown);
+              break;
             }
+
+            default:
+              // Delegate to browser.xml.
+              return this._receiveMessage(aMessage);
+              break;
           }
         ]]></body>
       </method>
 
       <!--
         For out-of-process code, event.screen[XY] is relative to the
         left/top of the content view. For in-process code,
         event.screen[XY] is relative to the left/top of the screen. We