mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
Bug 1806591: Implement scroll handoff for tree widgets r=mstriemer,masayuki
Differential Revision: https://phabricator.services.mozilla.com/D174051
This commit is contained in:
parent
90c4ec5697
commit
37bf49cf78
@ -132,6 +132,7 @@
|
||||
#include "mozilla/dom/BrowsingContextGroup.h"
|
||||
#include "mozilla/IMEStateManager.h"
|
||||
#include "mozilla/IMEContentObserver.h"
|
||||
#include "mozilla/WheelHandlingHelper.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
# include <direct.h>
|
||||
@ -4904,6 +4905,15 @@ nsDOMWindowUtils::GetOrientationLock(uint32_t* aOrientationLock) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::GetWheelScrollTarget(Element** aResult) {
|
||||
*aResult = nullptr;
|
||||
if (nsIFrame* targetFrame = WheelTransaction::GetScrollTargetFrame()) {
|
||||
NS_IF_ADDREF(*aResult = Element::FromNodeOrNull(targetFrame->GetContent()));
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::SetHiDPIMode(bool aHiDPI) {
|
||||
#ifdef DEBUG
|
||||
|
@ -2306,6 +2306,9 @@ interface nsIDOMWindowUtils : nsISupports {
|
||||
// Returns the current orientation lock value in browsing context.
|
||||
// This value is defined in hal/HalScreenConfiguration.h
|
||||
readonly attribute uint32_t orientationLock;
|
||||
|
||||
// Returns an element currently scrolling by wheel.
|
||||
Element getWheelScrollTarget();
|
||||
};
|
||||
|
||||
[scriptable, uuid(c694e359-7227-4392-a138-33c0cc1f15a6)]
|
||||
|
@ -195,6 +195,10 @@ skip-if = (os == 'mac' || os == 'win') # Bug 1141245, frequent timeouts on OSX 1
|
||||
[test_tooltip_noautohide.xhtml]
|
||||
[test_tree.xhtml]
|
||||
[test_tree_hier.xhtml]
|
||||
[test_tree_scroll.xhtml]
|
||||
support-files =
|
||||
!/gfx/layers/apz/test/mochitest/apz_test_utils.js
|
||||
!/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
|
||||
[test_tree_single.xhtml]
|
||||
[test_tree_view.xhtml]
|
||||
[test_window_intrinsic_size.xhtml]
|
||||
|
93
toolkit/content/tests/chrome/test_tree_scroll.xhtml
Normal file
93
toolkit/content/tests/chrome/test_tree_scroll.xhtml
Normal file
@ -0,0 +1,93 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
|
||||
<!--
|
||||
XUL Widget Test for scrolling behavior of tree
|
||||
-->
|
||||
<window title="Tree" width="500" height="600"
|
||||
onload="setTimeout(testtag_tree_scroll);"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
|
||||
|
||||
<script src="chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
|
||||
<script src="chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"></script>
|
||||
<script src="tree_shared.js"/>
|
||||
|
||||
<html:div style="height: 200px; overflow-y: scroll;">
|
||||
<html:div id="top" style="height: 50px; background: cyan;"></html:div>
|
||||
<tree rows="3">
|
||||
<treecols>
|
||||
<treecol id="name" label="label" sort="label" flex="1"/>
|
||||
</treecols>
|
||||
<treechildren>
|
||||
<treeitem>
|
||||
<treerow>
|
||||
<treecell label="0"/>
|
||||
</treerow>
|
||||
</treeitem>
|
||||
<treeitem>
|
||||
<treerow>
|
||||
<treecell label="1"/>
|
||||
</treerow>
|
||||
</treeitem>
|
||||
<treeitem>
|
||||
<treerow>
|
||||
<treecell label="2"/>
|
||||
</treerow>
|
||||
</treeitem>
|
||||
<treeitem>
|
||||
<treerow>
|
||||
<treecell label="3"/>
|
||||
</treerow>
|
||||
</treeitem>
|
||||
<treeitem>
|
||||
<treerow>
|
||||
<treecell label="4"/>
|
||||
</treerow>
|
||||
</treeitem>
|
||||
<treeitem>
|
||||
<treerow>
|
||||
<treecell label="5"/>
|
||||
</treerow>
|
||||
</treeitem>
|
||||
<treeitem>
|
||||
<treerow>
|
||||
<treecell label="6"/>
|
||||
</treerow>
|
||||
</treeitem>
|
||||
<treeitem>
|
||||
<treerow>
|
||||
<treecell label="7"/>
|
||||
</treerow>
|
||||
</treeitem>
|
||||
<treeitem>
|
||||
<treerow>
|
||||
<treecell label="8"/>
|
||||
</treerow>
|
||||
</treeitem>
|
||||
<treeitem>
|
||||
<treerow>
|
||||
<treecell label="9"/>
|
||||
</treerow>
|
||||
</treeitem>
|
||||
</treechildren>
|
||||
</tree>
|
||||
<html:div id="bottom" style="height: 150px; background: orange;"></html:div>
|
||||
</html:div>
|
||||
|
||||
<!-- test results are displayed in the html:body -->
|
||||
<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
|
||||
|
||||
<!-- test code goes here -->
|
||||
<script type="application/javascript"><![CDATA[
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
]]>
|
||||
</script>
|
||||
|
||||
</window>
|
@ -1753,6 +1753,194 @@ function testtag_tree_wheel(aTree) {
|
||||
is(defaultPrevented, 48, "wheel event default prevented");
|
||||
}
|
||||
|
||||
async function testtag_tree_scroll() {
|
||||
const tree = document.querySelector("tree");
|
||||
|
||||
info("Scroll down with the content scrollbar at the top");
|
||||
await doScrollTest({
|
||||
tree,
|
||||
initialTreeScrollRow: 0,
|
||||
initialContainerScrollTop: 0,
|
||||
scrollDelta: 10,
|
||||
isTreeScrollExpected: true,
|
||||
});
|
||||
|
||||
info("Scroll down with the content scrollbar at the middle");
|
||||
await doScrollTest({
|
||||
tree,
|
||||
initialTreeScrollRow: 3,
|
||||
initialContainerScrollTop: 0,
|
||||
scrollDelta: 10,
|
||||
isTreeScrollExpected: true,
|
||||
});
|
||||
|
||||
info("Scroll down with the content scrollbar at the bottom");
|
||||
await doScrollTest({
|
||||
tree,
|
||||
initialTreeScrollRow: 9,
|
||||
initialContainerScrollTop: 0,
|
||||
scrollDelta: 10,
|
||||
isTreeScrollExpected: false,
|
||||
});
|
||||
|
||||
info("Scroll up with the content scrollbar at the bottom");
|
||||
await doScrollTest({
|
||||
tree,
|
||||
initialTreeScrollRow: 9,
|
||||
initialContainerScrollTop: 50,
|
||||
scrollDelta: -10,
|
||||
isTreeScrollExpected: true,
|
||||
});
|
||||
|
||||
info("Scroll up with the content scrollbar at the middle");
|
||||
await doScrollTest({
|
||||
tree,
|
||||
initialTreeScrollRow: 5,
|
||||
initialContainerScrollTop: 50,
|
||||
scrollDelta: -10,
|
||||
isTreeScrollExpected: true,
|
||||
});
|
||||
|
||||
info("Scroll up with the content scrollbar at the top");
|
||||
await doScrollTest({
|
||||
tree,
|
||||
initialTreeScrollRow: 0,
|
||||
initialContainerScrollTop: 50,
|
||||
scrollDelta: -10,
|
||||
isTreeScrollExpected: false,
|
||||
});
|
||||
|
||||
info("Check whether the tree is not scrolled when the parent is scrolling");
|
||||
await doScrollWhileScrollingParent(tree);
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
async function doScrollWhileScrollingParent(tree) {
|
||||
const scrollbar = tree.shadowRoot.querySelector(
|
||||
"scrollbar[orient='vertical']"
|
||||
);
|
||||
const parent = tree.parentElement;
|
||||
|
||||
// Set initial scroll amount.
|
||||
tree.scrollToRow(0);
|
||||
parent.scrollTop = 0;
|
||||
|
||||
const scrollAmount = scrollbar.getAttribute("curpos");
|
||||
|
||||
// Scroll parent from top to bottom.
|
||||
const utils = SpecialPowers.getDOMWindowUtils(window);
|
||||
let isScrollSeriesTimeout = false;
|
||||
await SimpleTest.promiseWaitForCondition(async () => {
|
||||
await nativeScroll(parent, 10, 10, 10);
|
||||
if (!utils.getWheelScrollTarget()) {
|
||||
// Dependent on the environment, it might be over the timeout
|
||||
// (ScrollAnimationPhysics::kScrollSeriesTimeoutMs) that handles wheel
|
||||
// events as one series. If wheel event happened on the parent couldn't
|
||||
// be handled as one series of events, the tree component consumes the
|
||||
// event, this test will be invalid.
|
||||
isScrollSeriesTimeout = true;
|
||||
return true;
|
||||
}
|
||||
return parent.scrollTop === parent.scrollTopMax;
|
||||
});
|
||||
|
||||
if (!isScrollSeriesTimeout) {
|
||||
is(
|
||||
scrollAmount,
|
||||
scrollbar.getAttribute("curpos"),
|
||||
"The tree should not be scrolled"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function doScrollTest({
|
||||
tree,
|
||||
initialTreeScrollRow,
|
||||
initialContainerScrollTop,
|
||||
scrollDelta,
|
||||
isTreeScrollExpected,
|
||||
}) {
|
||||
const scrollbar = tree.shadowRoot.querySelector(
|
||||
"scrollbar[orient='vertical']"
|
||||
);
|
||||
const container = tree.parentElement;
|
||||
|
||||
// Set initial scroll amount.
|
||||
tree.scrollToRow(initialTreeScrollRow);
|
||||
container.scrollTop = initialContainerScrollTop;
|
||||
|
||||
const treeScrollAmount = scrollbar.getAttribute("curpos");
|
||||
const containerScrollAmount = container.scrollTop;
|
||||
|
||||
// Wait until changing either scroll.
|
||||
await SimpleTest.promiseWaitForCondition(async () => {
|
||||
await nativeScroll(tree, 10, 10, scrollDelta);
|
||||
return (
|
||||
treeScrollAmount !== scrollbar.getAttribute("curpos") ||
|
||||
containerScrollAmount !== container.scrollTop
|
||||
);
|
||||
});
|
||||
|
||||
is(
|
||||
treeScrollAmount !== scrollbar.getAttribute("curpos"),
|
||||
isTreeScrollExpected,
|
||||
"Scroll of tree is expected"
|
||||
);
|
||||
is(
|
||||
containerScrollAmount !== container.scrollTop,
|
||||
!isTreeScrollExpected,
|
||||
"Scroll of container is expected"
|
||||
);
|
||||
|
||||
// Wait until finishing wheel scroll transaction.
|
||||
const utils = SpecialPowers.getDOMWindowUtils(window);
|
||||
await SimpleTest.promiseWaitForCondition(() => !utils.getWheelScrollTarget());
|
||||
}
|
||||
|
||||
async function nativeScroll(component, offsetX, offsetY, scrollDelta) {
|
||||
const utils = SpecialPowers.getDOMWindowUtils(window);
|
||||
const x = component.screenX + offsetX;
|
||||
const y = component.screenY + offsetX;
|
||||
|
||||
// Mouse move event.
|
||||
await new Promise(resolve => {
|
||||
window.addEventListener("mousemove", resolve, { once: true });
|
||||
utils.sendNativeMouseEvent(
|
||||
x,
|
||||
y,
|
||||
utils.NATIVE_MOUSE_MESSAGE_MOVE,
|
||||
0,
|
||||
{},
|
||||
component
|
||||
);
|
||||
});
|
||||
|
||||
// Wheel event.
|
||||
await new Promise(resolve => {
|
||||
window.addEventListener("wheel", resolve, { once: true });
|
||||
utils.sendNativeMouseScrollEvent(
|
||||
x,
|
||||
y,
|
||||
// nativeVerticalWheelEventMsg is defined in apz_test_native_event_utils.js
|
||||
// eslint-disable-next-line no-undef
|
||||
nativeVerticalWheelEventMsg(),
|
||||
0,
|
||||
// nativeScrollUnits is defined in apz_test_native_event_utils.js
|
||||
// eslint-disable-next-line no-undef
|
||||
-nativeScrollUnits(component, scrollDelta),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
component
|
||||
);
|
||||
});
|
||||
|
||||
// promiseApzFlushedRepaints is defined in apz_test_utils.js
|
||||
// eslint-disable-next-line no-undef
|
||||
await promiseApzFlushedRepaints();
|
||||
}
|
||||
|
||||
function synthesizeColumnDrag(
|
||||
aTree,
|
||||
aMouseDownColumnNumber,
|
||||
|
@ -672,6 +672,10 @@
|
||||
el.addEventListener("command", stopProp);
|
||||
}
|
||||
this.shadowRoot.appendChild(this.constructor.fragment);
|
||||
|
||||
this.#verticalScrollbar = this.shadowRoot.querySelector(
|
||||
"scrollbar[orient='vertical']"
|
||||
);
|
||||
}
|
||||
|
||||
static get inheritedAttributes() {
|
||||
@ -787,31 +791,20 @@
|
||||
|
||||
// This event doesn't retarget, so listen on the shadow DOM directly
|
||||
this.shadowRoot.addEventListener("MozMousePixelScroll", event => {
|
||||
if (
|
||||
!(
|
||||
this.getAttribute("allowunderflowscroll") == "true" &&
|
||||
this.getAttribute("hidevscroll") == "true"
|
||||
)
|
||||
) {
|
||||
if (this.#canScroll(event)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// This event doesn't retarget, so listen on the shadow DOM directly
|
||||
this.shadowRoot.addEventListener("DOMMouseScroll", event => {
|
||||
if (
|
||||
!(
|
||||
this.getAttribute("allowunderflowscroll") == "true" &&
|
||||
this.getAttribute("hidevscroll") == "true"
|
||||
)
|
||||
) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (this._editingColumn) {
|
||||
if (!this.#canScroll(event)) {
|
||||
return;
|
||||
}
|
||||
if (event.axis == event.HORIZONTAL_AXIS) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (this._editingColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1377,7 +1370,7 @@
|
||||
// in LTR mode, and left side of the cell in RTL mode.
|
||||
let left = style.direction == "rtl" ? cellRect.x : textRect.x;
|
||||
let scrollbarWidth = window.windowUtils.getBoundsWithoutFlushing(
|
||||
this.shadowRoot.querySelector("scrollbar[orient='vertical']")
|
||||
this.#verticalScrollbar
|
||||
).width;
|
||||
// Note: this won't be quite right in RTL for trees using twisties
|
||||
// or indentation. bug 1708159 tracks fixing the implementation
|
||||
@ -1664,6 +1657,26 @@
|
||||
|
||||
return this.changeOpenState(this.currentIndex);
|
||||
}
|
||||
|
||||
#verticalScrollbar = null;
|
||||
|
||||
#canScroll(event) {
|
||||
if (
|
||||
window.windowUtils.getWheelScrollTarget() ||
|
||||
event.axis == event.HORIZONTAL_AXIS ||
|
||||
(this.getAttribute("allowunderflowscroll") == "true" &&
|
||||
this.getAttribute("hidevscroll") == "true")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const curpos = Number(this.#verticalScrollbar.getAttribute("curpos"));
|
||||
return (
|
||||
(event.detail < 0 && 0 < curpos) ||
|
||||
(event.detail > 0 &&
|
||||
curpos < Number(this.#verticalScrollbar.getAttribute("maxpos")))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MozXULElement.implementCustomInterface(MozTree, [
|
||||
|
Loading…
Reference in New Issue
Block a user