Bug 938359 - Back out changeset cc298e4b0f47 for regressions (a=backout)
authorBill McCloskey <wmccloskey@mozilla.com>
Thu, 27 Mar 2014 17:35:48 -0700
changeset 192357 83f1fa9a7ca903cadc97c6ecec8ccda6142f3c99
parent 192356 ce9cfb44f9c295e8d9d540d81cdffc6b8b933d5c
child 192358 f069459231018a95eac2fe7b5d3990b45d61af50
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)
reviewersbackout
bugs938359
milestone30.0a2
Bug 938359 - Back out changeset cc298e4b0f47 for regressions (a=backout)
browser/base/content/content.js
browser/metro/base/content/bindings/browser.xml
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,17 +3,16 @@
  * 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",
--- a/browser/metro/base/content/bindings/browser.xml
+++ b/browser/metro/base/content/bindings/browser.xml
@@ -63,16 +63,20 @@
 
       <field name="_documentURI">null</field>
       <property name="documentURI"
                 onget="return this._documentURI ? this._ios.newURI(this._documentURI, null, null) : null"
                 readonly="true"/>
 
       <field name="contentWindowId">null</field>
 
+      <property name="messageManager"
+                onget="return this._frameLoader.messageManager;"
+                readonly="true"/>
+
       <field name="_contentTitle">null</field>
 
       <field name="_ios">
          Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
       </field>
 
       <!--
         * Point Conversion Routines - browsers may be shifted by UI such that
deleted file mode 100644
--- a/toolkit/content/browser-content.js
+++ /dev/null
@@ -1,234 +0,0 @@
-/* -*- 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,17 +23,16 @@ 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,20 +70,17 @@ 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');
-
-      // Before continuing the test, we need to ensure that the IPC
-      // message that stops autoscrolling has had time to arrive.
-      executeSoon(nextTest);
+      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,22 +85,16 @@ 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, nsIMessageListener">
+    <implementation type="application/javascript" implements="nsIObserver, nsIDOMEventListener, nsIFrameRequestCallback">
       <property name="autoscrollEnabled">
         <getter>
           <![CDATA[
             if (this.getAttribute("autoscroll") == "false")
               return false;
 
             var enabled = true;
             try {
@@ -595,16 +595,24 @@
             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");
@@ -780,22 +788,16 @@
           }
           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>
@@ -836,44 +838,16 @@
           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;
@@ -892,35 +866,37 @@
 
             if (purge > 0)
               this.sessionHistory.PurgeHistory(purge);
           ]]>
         </body>
       </method>
 
       <field name="_AUTOSCROLL_SNAP">10</field>
-      <field name="_scrolling">false</field>
+      <field name="_scrollable">null</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._scrolling) {
-              this._scrolling = false;
+            if (this._scrollable) {
+              this._scrollable = null;
               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[
@@ -928,20 +904,19 @@
             var popup = document.createElementNS(XUL_NS, "panel");
             popup.className = "autoscroller";
             return popup;
           ]]>
         </body>
       </method>
 
       <method name="startScroll">
-        <parameter name="scrolldir"/>
-        <parameter name="screenX"/>
-        <parameter name="screenY"/>
-        <body><![CDATA[
+        <parameter name="event"/>
+        <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();
@@ -953,47 +928,231 @@
             // 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 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.addEventListener("popuphidden", this, true);
             this._autoScrollPopup.showPopup(document.documentElement,
-                                            screenX,
-                                            screenY,
+                                            event.screenX,
+                                            event.screenY,
                                             "popup", null, null);
             this._ignoreMouseEvents = true;
-            this._scrolling = true;
-            this._startX = screenX;
-            this._startY = screenY;
+            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;
 
             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>
+      </method>
+
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body>
         <![CDATA[
-          if (this._scrolling) {
+          if (this._scrollable) {
             switch(aEvent.type) {
               case "mousemove": {
-                var x = aEvent.screenX - this._startX;
-                var y = aEvent.screenY - this._startY;
+                this._screenX = aEvent.screenX;
+                this._screenY = aEvent.screenY;
+
+                var x = this._screenX - this._startX;
+                var y = this._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":
@@ -1150,16 +1309,27 @@
 
           // 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">
+    <implementation type="application/javascript" implements="nsIObserver, nsIDOMEventListener, nsIMessageListener, nsIMessageListener">
 
       <field name="_securityUI">null</field>
 
       <property name="securityUI"
                 readonly="true">
         <getter><![CDATA[
           if (!this._securityUI) {
             let jsm = "resource://gre/modules/RemoteSecurityUI.jsm";
@@ -260,23 +260,17 @@
             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