mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-03-04 07:40:42 +00:00
Bug 1597765 - support auto scroll in out of process frames r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D54596 --HG-- rename : toolkit/modules/AutoScrollController.jsm => toolkit/actors/AutoScrollChild.jsm extra : moz-landing-system : lando
This commit is contained in:
parent
f5fb2ad06d
commit
f9f8b6c906
@ -5,10 +5,12 @@
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var EXPORTED_SYMBOLS = ["AutoScrollController"];
|
||||
var EXPORTED_SYMBOLS = ["AutoScrollChild"];
|
||||
|
||||
class AutoScrollChild extends JSWindowActorChild {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
class AutoScrollController {
|
||||
constructor(global) {
|
||||
this._scrollable = null;
|
||||
this._scrolldir = "";
|
||||
this._startX = null;
|
||||
@ -18,10 +20,9 @@ class AutoScrollController {
|
||||
this._lastFrame = null;
|
||||
this._autoscrollHandledByApz = false;
|
||||
this._scrollId = null;
|
||||
this._global = global;
|
||||
this.autoscrollLoop = this.autoscrollLoop.bind(this);
|
||||
|
||||
global.addMessageListener("Autoscroll:Stop", this);
|
||||
this.observer = new AutoScrollObserver(this);
|
||||
this.autoscrollLoop = this.autoscrollLoop.bind(this);
|
||||
}
|
||||
|
||||
isAutoscrollBlocker(node) {
|
||||
@ -145,16 +146,22 @@ class AutoScrollController {
|
||||
this._scrolldir = direction;
|
||||
this._scrollable = aNode.ownerGlobal;
|
||||
} else if (aNode.ownerGlobal.frameElement) {
|
||||
// FIXME(emilio): This won't work with Fission.
|
||||
// Note, in case of out of process iframes frameElement is null, and
|
||||
// a caller is supposed to communicate to iframe's parent on its own to
|
||||
// support cross process scrolling.
|
||||
this.findNearestScrollableElement(aNode.ownerGlobal.frameElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startScroll(event) {
|
||||
async startScroll(event) {
|
||||
this.findNearestScrollableElement(event.originalTarget);
|
||||
|
||||
if (!this._scrollable) {
|
||||
this.sendAsyncMessage("Autoscroll:MaybeStartInParent", {
|
||||
browsingContextId: this.browsingContext.id,
|
||||
screenX: event.screenX,
|
||||
screenY: event.screenY,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -180,20 +187,23 @@ class AutoScrollController {
|
||||
// No view ID - leave this._scrollId as null. Receiving side will check.
|
||||
}
|
||||
let presShellId = domUtils.getPresShellId();
|
||||
let [result] = this._global.sendSyncMessage("Autoscroll:Start", {
|
||||
scrolldir: this._scrolldir,
|
||||
screenX: event.screenX,
|
||||
screenY: event.screenY,
|
||||
scrollId: this._scrollId,
|
||||
presShellId,
|
||||
});
|
||||
if (!result.autoscrollEnabled) {
|
||||
let { autoscrollEnabled, usingApz } = await this.sendQuery(
|
||||
"Autoscroll:Start",
|
||||
{
|
||||
scrolldir: this._scrolldir,
|
||||
screenX: event.screenX,
|
||||
screenY: event.screenY,
|
||||
scrollId: this._scrollId,
|
||||
presShellId,
|
||||
}
|
||||
);
|
||||
if (!autoscrollEnabled) {
|
||||
this._scrollable = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Services.els.addSystemEventListener(this._global, "mousemove", this, true);
|
||||
this._global.addEventListener("pagehide", this, true);
|
||||
Services.els.addSystemEventListener(this.document, "mousemove", this, true);
|
||||
this.document.addEventListener("pagehide", this, true);
|
||||
|
||||
this._ignoreMouseEvents = true;
|
||||
this._startX = event.screenX;
|
||||
@ -202,9 +212,9 @@ class AutoScrollController {
|
||||
this._screenY = event.screenY;
|
||||
this._scrollErrorX = 0;
|
||||
this._scrollErrorY = 0;
|
||||
this._autoscrollHandledByApz = result.usingApz;
|
||||
this._autoscrollHandledByApz = usingApz;
|
||||
|
||||
if (!result.usingApz) {
|
||||
if (!usingApz) {
|
||||
// If the browser didn't hand the autoscroll off to APZ,
|
||||
// scroll here in the main thread.
|
||||
this.startMainThreadScroll();
|
||||
@ -212,12 +222,16 @@ class AutoScrollController {
|
||||
// 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");
|
||||
Services.obs.addObserver(this.observer, "autoscroll-rejected-by-apz");
|
||||
}
|
||||
|
||||
if (Cu.isInAutomation) {
|
||||
Services.obs.notifyObservers(content, "autoscroll-start");
|
||||
}
|
||||
}
|
||||
|
||||
startMainThreadScroll() {
|
||||
let content = this._global.content;
|
||||
let content = this.document.defaultView;
|
||||
this._lastFrame = content.performance.now();
|
||||
content.requestAnimationFrame(this.autoscrollLoop);
|
||||
}
|
||||
@ -228,14 +242,17 @@ class AutoScrollController {
|
||||
this._scrollable = null;
|
||||
|
||||
Services.els.removeSystemEventListener(
|
||||
this._global,
|
||||
this.document,
|
||||
"mousemove",
|
||||
this,
|
||||
true
|
||||
);
|
||||
this._global.removeEventListener("pagehide", this, true);
|
||||
this.document.removeEventListener("pagehide", this, true);
|
||||
if (this._autoscrollHandledByApz) {
|
||||
Services.obs.removeObserver(this, "autoscroll-rejected-by-apz");
|
||||
Services.obs.removeObserver(
|
||||
this.observer,
|
||||
"autoscroll-rejected-by-apz"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -306,6 +323,8 @@ class AutoScrollController {
|
||||
this._screenY = event.screenY;
|
||||
} else if (event.type == "mousedown") {
|
||||
if (
|
||||
event.isTrusted & !event.defaultPrevented &&
|
||||
event.button == 1 &&
|
||||
!this._scrollable &&
|
||||
!this.isAutoscrollBlocker(event.originalTarget)
|
||||
) {
|
||||
@ -315,14 +334,28 @@ class AutoScrollController {
|
||||
if (this._scrollable) {
|
||||
var doc = this._scrollable.ownerDocument || this._scrollable.document;
|
||||
if (doc == event.target) {
|
||||
this._global.sendAsyncMessage("Autoscroll:Cancel");
|
||||
this.sendAsyncMessage("Autoscroll:Cancel");
|
||||
this.stopScroll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
receiveMessage(msg) {
|
||||
let data = msg.data;
|
||||
switch (msg.name) {
|
||||
case "Autoscroll:MaybeStart":
|
||||
for (let child of this.browsingContext.getChildren()) {
|
||||
if (data.browsingContextId == child.id) {
|
||||
this.startScroll({
|
||||
screenX: data.screenX,
|
||||
screenY: data.screenY,
|
||||
originalTarget: child.embedderElement,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "Autoscroll:Stop": {
|
||||
this.stopScroll();
|
||||
break;
|
||||
@ -330,14 +363,24 @@ class AutoScrollController {
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
rejectedByApz(data) {
|
||||
// The caller passes in the scroll id via 'data'.
|
||||
if (data == this._scrollId) {
|
||||
this._autoscrollHandledByApz = false;
|
||||
this.startMainThreadScroll();
|
||||
Services.obs.removeObserver(this.observer, "autoscroll-rejected-by-apz");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AutoScrollObserver {
|
||||
constructor(actor) {
|
||||
this.actor = actor;
|
||||
}
|
||||
|
||||
observe(subject, topic, data) {
|
||||
if (topic === "autoscroll-rejected-by-apz") {
|
||||
this.actor.rejectedByApz(data);
|
||||
}
|
||||
}
|
||||
}
|
33
toolkit/actors/AutoScrollParent.jsm
Normal file
33
toolkit/actors/AutoScrollParent.jsm
Normal file
@ -0,0 +1,33 @@
|
||||
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["AutoScrollParent"];
|
||||
|
||||
class AutoScrollParent extends JSWindowActorParent {
|
||||
receiveMessage(msg) {
|
||||
let browser = this.manager.browsingContext.top.embedderElement;
|
||||
if (!browser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let data = msg.data;
|
||||
switch (msg.name) {
|
||||
case "Autoscroll:Start":
|
||||
return Promise.resolve(browser.startScroll(data));
|
||||
case "Autoscroll:MaybeStartInParent":
|
||||
let parent = this.browsingContext.parent;
|
||||
if (parent) {
|
||||
let actor = parent.currentWindowGlobal.getActor("AutoScroll");
|
||||
actor.sendAsyncMessage("Autoscroll:MaybeStart", data);
|
||||
}
|
||||
break;
|
||||
case "Autoscroll:Cancel":
|
||||
browser.cancelScroll();
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -7,6 +7,9 @@
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Toolkit', 'General')
|
||||
|
||||
with Files('AutoScroll*.jsm'):
|
||||
BUG_COMPONENT = ('Core', 'Panning and Zooming')
|
||||
|
||||
with Files('Finder*.jsm'):
|
||||
BUG_COMPONENT = ('Toolkit', 'Find Toolbar')
|
||||
|
||||
@ -25,6 +28,8 @@ FINAL_TARGET_FILES.actors += [
|
||||
'AutoCompleteParent.jsm',
|
||||
'AutoplayChild.jsm',
|
||||
'AutoplayParent.jsm',
|
||||
'AutoScrollChild.jsm',
|
||||
'AutoScrollParent.jsm',
|
||||
'BrowserElementChild.jsm',
|
||||
'BrowserElementParent.jsm',
|
||||
'ControllersChild.jsm',
|
||||
|
@ -6,34 +6,11 @@
|
||||
/* eslint-env mozilla/frame-script */
|
||||
/* eslint no-unused-vars: ["error", {args: "none"}] */
|
||||
|
||||
/* exported Services */
|
||||
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var { ActorManagerChild } = ChromeUtils.import(
|
||||
"resource://gre/modules/ActorManagerChild.jsm"
|
||||
);
|
||||
|
||||
ActorManagerChild.attach(this);
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"AutoScrollController",
|
||||
"resource://gre/modules/AutoScrollController.jsm"
|
||||
);
|
||||
|
||||
var global = this;
|
||||
|
||||
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
|
||||
);
|
||||
|
@ -160,6 +160,26 @@ body > div > div {width: 1000px;height: 1000px;}\
|
||||
expected: expectScrollNone,
|
||||
testwindow: true,
|
||||
},
|
||||
{
|
||||
// Test: scroll is initiated in out of process iframe having no scrollable area
|
||||
dataUri:
|
||||
"data:text/html," +
|
||||
encodeURIComponent(`
|
||||
<!doctype html>
|
||||
<head><meta content="text/html;charset=utf-8"></head><body>
|
||||
<div id="scroller" style="width: 300px; height: 300px; overflow-y: scroll; overflow-x: hidden; border: solid 1px blue;">
|
||||
<iframe id="noscroll-outofprocess-iframe" src="https://example.com/browser/toolkit/content/tests/browser/file_contentTitle.html"
|
||||
style="border: solid 1px green; margin: 2px;"></iframe>
|
||||
<div style="width: 100%; height: 200px;"></div>
|
||||
</div></body>
|
||||
`),
|
||||
},
|
||||
{
|
||||
elem: "noscroll-outofprocess-iframe",
|
||||
// We expect the div to scroll vertically, not the iframe's window.
|
||||
expected: expectScrollVert,
|
||||
scrollable: "scroller",
|
||||
},
|
||||
];
|
||||
|
||||
for (let test of allTests) {
|
||||
@ -187,19 +207,28 @@ body > div > div {width: 1000px;height: 1000px;}\
|
||||
|
||||
// This ensures bug 605127 is fixed: pagehide in an unrelated document
|
||||
// should not cancel the autoscroll.
|
||||
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function() {
|
||||
var iframe = content.document.getElementById("iframe");
|
||||
await ContentTask.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
{ waitForAutoScrollStart: test.expected != expectScrollNone },
|
||||
async ({ waitForAutoScrollStart }) => {
|
||||
var iframe = content.document.getElementById("iframe");
|
||||
|
||||
if (iframe) {
|
||||
var e = new iframe.contentWindow.PageTransitionEvent("pagehide", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
persisted: false,
|
||||
});
|
||||
iframe.contentDocument.dispatchEvent(e);
|
||||
iframe.contentDocument.documentElement.dispatchEvent(e);
|
||||
if (iframe) {
|
||||
var e = new iframe.contentWindow.PageTransitionEvent("pagehide", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
persisted: false,
|
||||
});
|
||||
iframe.contentDocument.dispatchEvent(e);
|
||||
iframe.contentDocument.documentElement.dispatchEvent(e);
|
||||
}
|
||||
if (waitForAutoScrollStart) {
|
||||
await new Promise(resolve =>
|
||||
Services.obs.addObserver(resolve, "autoscroll-start")
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
is(
|
||||
document.activeElement,
|
||||
@ -265,7 +294,7 @@ body > div > div {width: 1000px;height: 1000px;}\
|
||||
{
|
||||
scrollVert,
|
||||
scrollHori,
|
||||
elemid: test.elem,
|
||||
elemid: test.scrollable || test.elem,
|
||||
checkWindow: test.testwindow,
|
||||
},
|
||||
],
|
||||
|
@ -1344,8 +1344,6 @@
|
||||
"PopupBlocking:UpdateBlockedPopups",
|
||||
this
|
||||
);
|
||||
this.messageManager.addMessageListener("Autoscroll:Start", this);
|
||||
this.messageManager.addMessageListener("Autoscroll:Cancel", this);
|
||||
this.messageManager.addMessageListener(
|
||||
"UnselectedTabHover:Toggle",
|
||||
this
|
||||
@ -1418,41 +1416,6 @@
|
||||
this.updateBlockedPopups();
|
||||
break;
|
||||
}
|
||||
case "Autoscroll:Start": {
|
||||
if (!this.autoscrollEnabled) {
|
||||
return { autoscrollEnabled: false, usingApz: false };
|
||||
}
|
||||
this.startScroll(data.scrolldir, data.screenX, data.screenY);
|
||||
let usingApz = false;
|
||||
if (
|
||||
this.isRemoteBrowser &&
|
||||
data.scrollId != null &&
|
||||
this.mPrefs.getBoolPref("apz.autoscroll.enabled", false)
|
||||
) {
|
||||
let { remoteTab } = this.frameLoader;
|
||||
if (remoteTab) {
|
||||
// If APZ is handling the autoscroll, it may decide to cancel
|
||||
// it of its own accord, so register an observer to allow it
|
||||
// to notify us of that.
|
||||
var os = Services.obs;
|
||||
os.addObserver(this.observer, "apz:cancel-autoscroll", true);
|
||||
|
||||
usingApz = remoteTab.startApzAutoscroll(
|
||||
data.screenX,
|
||||
data.screenY,
|
||||
data.scrollId,
|
||||
data.presShellId
|
||||
);
|
||||
}
|
||||
// Save the IDs for later
|
||||
this._autoScrollScrollId = data.scrollId;
|
||||
this._autoScrollPresShellId = data.presShellId;
|
||||
}
|
||||
return { autoscrollEnabled: true, usingApz };
|
||||
}
|
||||
case "Autoscroll:Cancel":
|
||||
this._autoScrollPopup.hidePopup();
|
||||
break;
|
||||
case "UnselectedTabHover:Toggle":
|
||||
this._shouldSendUnselectedTabHover = data.enable
|
||||
? ++this._unselectedTabHoverMessageListenerCount > 0
|
||||
@ -1659,7 +1622,8 @@
|
||||
window.removeEventListener("keydown", this, true);
|
||||
window.removeEventListener("keypress", this, true);
|
||||
window.removeEventListener("keyup", this, true);
|
||||
this.messageManager.sendAsyncMessage("Autoscroll:Stop");
|
||||
|
||||
this.sendMessageToActor("Autoscroll:Stop", {}, "AutoScroll", true);
|
||||
|
||||
try {
|
||||
Services.obs.removeObserver(this.observer, "apz:cancel-autoscroll");
|
||||
@ -1690,7 +1654,11 @@
|
||||
return popup;
|
||||
}
|
||||
|
||||
startScroll(scrolldir, screenX, screenY) {
|
||||
startScroll({ scrolldir, screenX, screenY, scrollId, presShellId }) {
|
||||
if (!this.autoscrollEnabled) {
|
||||
return { autoscrollEnabled: false, usingApz: false };
|
||||
}
|
||||
|
||||
const POPUP_SIZE = 32;
|
||||
if (!this._autoScrollPopup) {
|
||||
if (this.hasAttribute("autoscrollpopup")) {
|
||||
@ -1772,6 +1740,40 @@
|
||||
window.addEventListener("keydown", this, true);
|
||||
window.addEventListener("keypress", this, true);
|
||||
window.addEventListener("keyup", this, true);
|
||||
|
||||
let usingApz = false;
|
||||
if (
|
||||
this.isRemoteBrowser &&
|
||||
scrollId != null &&
|
||||
this.mPrefs.getBoolPref("apz.autoscroll.enabled", false)
|
||||
) {
|
||||
let { remoteTab } = this.frameLoader;
|
||||
if (remoteTab) {
|
||||
// If APZ is handling the autoscroll, it may decide to cancel
|
||||
// it of its own accord, so register an observer to allow it
|
||||
// to notify us of that.
|
||||
Services.obs.addObserver(
|
||||
this.observer,
|
||||
"apz:cancel-autoscroll",
|
||||
true
|
||||
);
|
||||
|
||||
usingApz = remoteTab.startApzAutoscroll(
|
||||
screenX,
|
||||
screenY,
|
||||
scrollId,
|
||||
presShellId
|
||||
);
|
||||
}
|
||||
// Save the IDs for later
|
||||
this._autoScrollScrollId = scrollId;
|
||||
this._autoScrollPresShellId = presShellId;
|
||||
}
|
||||
return { autoscrollEnabled: true, usingApz };
|
||||
}
|
||||
|
||||
cancelScroll() {
|
||||
this._autoScrollPopup.hidePopup();
|
||||
}
|
||||
|
||||
handleEvent(aEvent) {
|
||||
|
@ -90,6 +90,21 @@ let ACTORS = {
|
||||
allFrames: true,
|
||||
},
|
||||
|
||||
AutoScroll: {
|
||||
parent: {
|
||||
moduleURI: "resource://gre/actors/AutoScrollParent.jsm",
|
||||
},
|
||||
|
||||
child: {
|
||||
moduleURI: "resource://gre/actors/AutoScrollChild.jsm",
|
||||
events: {
|
||||
mousedown: { capture: true, mozSystemGroup: true },
|
||||
},
|
||||
},
|
||||
|
||||
allFrames: true,
|
||||
},
|
||||
|
||||
BrowserElement: {
|
||||
parent: {
|
||||
moduleURI: "resource://gre/actors/BrowserElementParent.jsm",
|
||||
|
@ -48,9 +48,6 @@ with Files('tests/xpcshell/test_UpdateUtils*.js'):
|
||||
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')
|
||||
|
||||
@ -165,7 +162,6 @@ EXTRA_JS_MODULES += [
|
||||
'ActorManagerParent.jsm',
|
||||
'AppMenuNotifications.jsm',
|
||||
'AsyncPrefs.jsm',
|
||||
'AutoScrollController.jsm',
|
||||
'BinarySearch.jsm',
|
||||
'BrowserUtils.jsm',
|
||||
'CanonicalJSON.jsm',
|
||||
|
Loading…
x
Reference in New Issue
Block a user