Bug 1467278 - Lazily instantiate the AutoScrollController when a middle click occurs. r=kats,Gijs draft
authorFelipe Gomes <felipc@gmail.com>
Sat, 09 Jun 2018 18:42:51 -0300
changeset 806457 668638ccf6b6e846fa921e65cbbc7bdc89defaf3
parent 806456 3872a4523e9a4b1dba88be450a8fe63ac7be19a9
child 806458 89cbaa19e74a904d39087de2cdfbc2789c40057f
push id112894
push userfelipc@gmail.com
push dateSat, 09 Jun 2018 21:44:46 +0000
reviewerskats, Gijs
bugs1467278
milestone62.0a1
Bug 1467278 - Lazily instantiate the AutoScrollController when a middle click occurs. r=kats,Gijs MozReview-Commit-ID: BCpAHzMcubP
toolkit/content/browser-content.js
toolkit/modules/AutoScrollController.jsm
toolkit/modules/moz.build
--- a/toolkit/content/browser-content.js
+++ b/toolkit/content/browser-content.js
@@ -5,16 +5,18 @@
 
 /* eslint-env mozilla/frame-script */
 /* eslint no-unused-vars: ["error", {args: "none"}] */
 /* global sendAsyncMessage */
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+ChromeUtils.defineModuleGetter(this, "AutoScrollController",
+  "resource://gre/modules/AutoScrollController.jsm");
 ChromeUtils.defineModuleGetter(this, "BrowserUtils",
   "resource://gre/modules/BrowserUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "SelectContentHelper",
   "resource://gre/modules/SelectContentHelper.jsm");
 ChromeUtils.defineModuleGetter(this, "FindContent",
   "resource://gre/modules/FindContent.jsm");
 ChromeUtils.defineModuleGetter(this, "PrintingContent",
   "resource://gre/modules/PrintingContent.jsm");
@@ -28,16 +30,30 @@ var global = this;
 
 
 // Lazily load the finder code
 addMessageListener("Finder:Initialize", function() {
   let {RemoteFinderListener} = ChromeUtils.import("resource://gre/modules/RemoteFinder.jsm", {});
   new RemoteFinderListener(global);
 });
 
+var AutoScrollListener = {
+  handleEvent(event) {
+    if (event.isTrusted &
+        !event.defaultPrevented &&
+        event.button == 1) {
+      if (!this._controller) {
+        this._controller = new AutoScrollController(global);
+      }
+      this._controller.handleEvent(event);
+    }
+  }
+};
+Services.els.addSystemEventListener(global, "mousedown", AutoScrollListener, true);
+
 var PopupBlocking = {
   popupData: null,
   popupDataInternal: null,
 
   init() {
     addEventListener("DOMPopupBlocked", this, true);
     addEventListener("pageshow", this, true);
     addEventListener("pagehide", this, true);
--- a/toolkit/modules/AutoScrollController.jsm
+++ b/toolkit/modules/AutoScrollController.jsm
@@ -1,39 +1,41 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
 
+/* eslint no-unused-vars: ["error", {args: "none"}] */
+
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var EXPORTED_SYMBOLS = ["AutoScrollController"];
 
-var AutoScrollController = {
-  init: function init() {
+class AutoScrollController {
+  constructor(global) {
     this._scrollable = null;
     this._scrolldir = "";
     this._startX = null;
     this._startY = null;
     this._screenX = null;
     this._screenY = null;
     this._lastFrame = null;
     this._autoscrollHandledByApz = false;
     this._scrollId = null;
+    this._global = global;
     this.autoscrollLoop = this.autoscrollLoop.bind(this);
 
-    Services.els.addSystemEventListener(global, "mousedown", this, true);
-
-    addMessageListener("Autoscroll:Stop", this);
-  },
+    global.addMessageListener("Autoscroll:Stop", this);
+  }
 
   isAutoscrollBlocker(node) {
     let mmPaste = Services.prefs.getBoolPref("middlemouse.paste");
     let mmScrollbarPosition = Services.prefs.getBoolPref("middlemouse.scrollbarPosition");
+    let content = node.ownerGlobal;
 
     while (node) {
       if ((node instanceof content.HTMLAnchorElement || node instanceof content.HTMLAreaElement) &&
           node.hasAttribute("href")) {
         return true;
       }
 
       if (mmPaste && (node instanceof content.HTMLInputElement ||
@@ -44,42 +46,46 @@ var AutoScrollController = {
       if (node instanceof content.XULElement && mmScrollbarPosition
           && (node.localName == "scrollbar" || node.localName == "scrollcorner")) {
         return true;
       }
 
       node = node.parentNode;
     }
     return false;
-  },
+  }
 
   isScrollableElement(aNode) {
+    let content = aNode.ownerGlobal;
     if (aNode instanceof content.HTMLElement) {
       return !(aNode instanceof content.HTMLSelectElement) || aNode.multiple;
     }
 
     return aNode instanceof content.XULElement;
-  },
+  }
 
   getXBLNodes(parent, array) {
+    let content = parent.ownerGlobal;
     let anonNodes = content.document.getAnonymousNodes(parent);
     let nodes = Array.from(anonNodes || parent.childNodes || []);
     for (let node of nodes) {
       if (node.nodeName == "children") {
         return true;
       }
       if (this.getXBLNodes(node, array)) {
         array.push(node);
         return true;
       }
     }
     return false;
-  },
+  }
 
   * parentNodeIterator(aNode) {
+    let content = aNode.ownerGlobal;
+
     while (aNode) {
       yield aNode;
 
       let parent = aNode.parentNode;
       if (parent && parent instanceof content.XULElement) {
         let anonNodes = content.document.getAnonymousNodes(parent);
         if (anonNodes && !Array.from(anonNodes).includes(aNode)) {
           // XBL elements are skipped by parentNode property.
@@ -89,19 +95,21 @@ var AutoScrollController = {
           for (let node of nodes) {
             yield node;
           }
         }
       }
 
       aNode = parent;
     }
-  },
+  }
 
   findNearestScrollableElement(aNode) {
+    let content = aNode.ownerGlobal;
+
     // 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
     this._scrollable = null;
     for (let node of this.parentNodeIterator(aNode)) {
       // do not use overflow based autoscroll for <html> and <body>
@@ -147,25 +155,27 @@ var AutoScrollController = {
       } else if (this._scrollable.scrollMaxY != this._scrollable.scrollMinY) {
         this._scrolldir = "NS";
       } else if (this._scrollable.frameElement) {
         this.findNearestScrollableElement(this._scrollable.frameElement);
       } else {
         this._scrollable = null; // abort scrolling
       }
     }
-  },
+  }
 
   startScroll(event) {
 
     this.findNearestScrollableElement(event.originalTarget);
 
     if (!this._scrollable)
       return;
 
+    let content = event.originalTarget.ownerGlobal;
+
     // In some configurations like Print Preview, content.performance
     // (which we use below) is null. Autoscrolling is broken in Print
     // Preview anyways (see bug 1393494), so just don't start it at all.
     if (!content.performance)
       return;
 
     let domUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
                           .getInterface(Ci.nsIDOMWindowUtils);
@@ -176,29 +186,29 @@ var AutoScrollController = {
     }
     this._scrollId = null;
     try {
       this._scrollId = domUtils.getViewId(scrollable);
     } catch (e) {
       // No view ID - leave this._scrollId as null. Receiving side will check.
     }
     let presShellId = domUtils.getPresShellId();
-    let [result] = sendSyncMessage("Autoscroll:Start",
-                                   {scrolldir: this._scrolldir,
-                                    screenX: event.screenX,
-                                    screenY: event.screenY,
-                                    scrollId: this._scrollId,
-                                    presShellId});
+    let [result] = this._global.sendSyncMessage("Autoscroll:Start",
+                                                {scrolldir: this._scrolldir,
+                                                 screenX: event.screenX,
+                                                 screenY: event.screenY,
+                                                 scrollId: this._scrollId,
+                                                 presShellId});
     if (!result.autoscrollEnabled) {
       this._scrollable = null;
       return;
     }
 
-    Services.els.addSystemEventListener(global, "mousemove", this, true);
-    addEventListener("pagehide", this, true);
+    Services.els.addSystemEventListener(this._global, "mousemove", this, true);
+    this._global.addEventListener("pagehide", this, true);
 
     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;
@@ -209,52 +219,53 @@ var AutoScrollController = {
       // scroll here in the main thread.
       this.startMainThreadScroll();
     } else {
       // Even if the browser did hand the autoscroll to APZ,
       // APZ might reject it in which case it will notify us
       // and we need to take over.
       Services.obs.addObserver(this, "autoscroll-rejected-by-apz");
     }
-  },
+  }
 
   startMainThreadScroll() {
+    let content = this._global.content;
     this._lastFrame = content.performance.now();
     content.requestAnimationFrame(this.autoscrollLoop);
-  },
+  }
 
   stopScroll() {
     if (this._scrollable) {
       this._scrollable.mozScrollSnap();
       this._scrollable = null;
 
-      Services.els.removeSystemEventListener(global, "mousemove", this, true);
-      removeEventListener("pagehide", this, true);
+      Services.els.removeSystemEventListener(this._global, "mousemove", this, true);
+      this._global.removeEventListener("pagehide", this, true);
       if (this._autoscrollHandledByApz) {
         Services.obs.removeObserver(this, "autoscroll-rejected-by-apz");
       }
     }
-  },
+  }
 
   accelerate(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(num) {
     if (num > 0)
       return Math.floor(num);
     return Math.ceil(num);
-  },
+  }
 
   autoscrollLoop(timestamp) {
     if (!this._scrollable) {
       // Scrolling has been canceled
       return;
     }
 
     // avoid long jumps when the browser hangs for more than
@@ -283,54 +294,51 @@ var AutoScrollController = {
     }
 
     this._scrollable.scrollBy({
       left: actualScrollX,
       top: actualScrollY,
       behavior: "instant"
     });
 
-    content.requestAnimationFrame(this.autoscrollLoop);
-  },
+    this._scrollable.ownerGlobal.requestAnimationFrame(this.autoscrollLoop);
+  }
 
   handleEvent(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 &&
+      if (!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");
+          this._global.sendAsyncMessage("Autoscroll:Cancel");
         }
       }
     }
-  },
+  }
 
   receiveMessage(msg) {
     switch (msg.name) {
       case "Autoscroll:Stop": {
         this.stopScroll();
         break;
       }
     }
-  },
+  }
 
   observe(subject, topic, data) {
     if (topic === "autoscroll-rejected-by-apz") {
       // The caller passes in the scroll id via 'data'.
       if (data == this._scrollId) {
         this._autoscrollHandledByApz = false;
         this.startMainThreadScroll();
         Services.obs.removeObserver(this, "autoscroll-rejected-by-apz");
       }
     }
-  },
-};
+  }
+}
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -47,16 +47,19 @@ with Files('tests/xpcshell/test_UpdateUt
     BUG_COMPONENT = ('Toolkit', 'Application Update')
 
 with Files('tests/xpcshell/test_client_id.js'):
     BUG_COMPONENT = ('Toolkit', 'Telemetry')
 
 with Files('AsyncPrefs.jsm'):
     BUG_COMPONENT = ('Core', 'Security: Process Sandboxing')
 
+with Files('AutoScrollController.jsm'):
+    BUG_COMPONENT = ('Core', 'Panning and Zooming')
+
 with Files('CharsetMenu.jsm'):
     BUG_COMPONENT = ('Firefox', 'Toolbars and Customization')
 
 with Files('ClientID.jsm'):
     BUG_COMPONENT = ('Toolkit', 'Telemetry')
 
 with Files('Color.jsm'):
     BUG_COMPONENT = ('Toolkit', 'Find Toolbar')
@@ -175,16 +178,17 @@ EXTRA_JS_MODULES += [
     'addons/WebNavigationContent.js',
     'addons/WebNavigationFrames.jsm',
     'addons/WebRequest.jsm',
     'addons/WebRequestCommon.jsm',
     'addons/WebRequestContent.js',
     'addons/WebRequestUpload.jsm',
     'AppMenuNotifications.jsm',
     'AsyncPrefs.jsm',
+    'AutoScrollController.jsm',
     'Battery.jsm',
     'BinarySearch.jsm',
     'BrowserUtils.jsm',
     'CanonicalJSON.jsm',
     'CertUtils.jsm',
     'CharsetMenu.jsm',
     'ClientID.jsm',
     'Color.jsm',