Bug 1467278 - Lazily instantiate the AutoScrollController when a middle click occurs. r=kats,Gijs
MozReview-Commit-ID: BCpAHzMcubP
--- 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',