toolkit/content/widgets/browser.xml
changeset 173619 cc298e4b0f470d1ed70cd92e874b761c58b60fe8
parent 173466 17a33bb88521b4c62a25d871ddd567c3c969f9da
child 174054 25d89e7774d9d23e0fda4c6a4bc4580560f0fc64
--- 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();
@@ -928,231 +953,47 @@
             // 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.addEventListener("popuphidden", this, true);
             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>
-      </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":
@@ -1309,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.