summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:29 +0000
commit59203c63bb777a3bacec32fb8830fba33540e809 (patch)
tree58298e711c0ff0575818c30485b44a2f21bf28a0 /gfx/layers/apz
parentAdding upstream version 126.0.1. (diff)
downloadfirefox-59203c63bb777a3bacec32fb8830fba33540e809.tar.xz
firefox-59203c63bb777a3bacec32fb8830fba33540e809.zip
Adding upstream version 127.0.upstream/127.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/layers/apz')
-rw-r--r--gfx/layers/apz/src/AsyncPanZoomController.cpp10
-rw-r--r--gfx/layers/apz/src/FocusState.cpp1
-rw-r--r--gfx/layers/apz/src/InputBlockState.h4
-rw-r--r--gfx/layers/apz/src/InputQueue.cpp14
-rw-r--r--gfx/layers/apz/test/gtest/TestGestureDetector.cpp100
-rw-r--r--gfx/layers/apz/test/mochitest/helper_doubletap_zoom_textarea.html6
-rw-r--r--gfx/layers/apz/test/mochitest/helper_scroll_over_subframe.html146
-rw-r--r--gfx/layers/apz/test/mochitest/helper_scroll_over_subframe_child.html29
-rw-r--r--gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html2
-rw-r--r--gfx/layers/apz/test/mochitest/test_group_pointerevents.html9
-rw-r--r--gfx/layers/apz/test/mochitest/test_group_wheelevents.html11
11 files changed, 320 insertions, 12 deletions
diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp
index a070340421..84b4a95b9e 100644
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1988,6 +1988,10 @@ ParentLayerPoint AsyncPanZoomController::GetScrollWheelDelta(
// Apply user-set multipliers.
delta.x *= aMultiplierX;
delta.y *= aMultiplierY;
+ APZC_LOGV(
+ "user-multiplied delta is %s (deltaType %d, line size %s, page size %s)",
+ ToString(delta).c_str(), (int)aEvent.mDeltaType,
+ ToString(scrollAmount).c_str(), ToString(pageScrollSize).c_str());
// For the conditions under which we allow system scroll overrides, see
// WidgetWheelEvent::OverriddenDelta{X,Y}.
@@ -1999,6 +2003,7 @@ ParentLayerPoint AsyncPanZoomController::GetScrollWheelDelta(
aEvent.mAllowToOverrideSystemScrollSpeed) {
delta.x = WidgetWheelEvent::ComputeOverriddenDelta(delta.x, false);
delta.y = WidgetWheelEvent::ComputeOverriddenDelta(delta.y, true);
+ APZC_LOGV("overridden delta is %s", ToString(delta).c_str());
}
// If this is a line scroll, and this event was part of a scroll series, then
@@ -2258,6 +2263,8 @@ bool AsyncPanZoomController::AllowOneTouchPinch() const {
// Return whether or not the underlying layer can be scrolled on either axis.
bool AsyncPanZoomController::CanScroll(const InputData& aEvent) const {
ParentLayerPoint delta = GetDeltaForEvent(aEvent);
+ APZC_LOGV_DETAIL("CanScroll: event delta is %s", this,
+ ToString(delta).c_str());
if (!delta.x && !delta.y) {
return false;
}
@@ -2330,6 +2337,9 @@ bool AsyncPanZoomController::CanScrollWithWheel(
disregardedDirection != Some(ScrollDirection::eVertical)) {
return true;
}
+ APZC_LOGV_FM(Metrics(),
+ "cannot scroll with wheel (disregarded direction is %s)",
+ ToString(disregardedDirection).c_str());
return false;
}
diff --git a/gfx/layers/apz/src/FocusState.cpp b/gfx/layers/apz/src/FocusState.cpp
index 0230676e7e..84bf0b4570 100644
--- a/gfx/layers/apz/src/FocusState.cpp
+++ b/gfx/layers/apz/src/FocusState.cpp
@@ -7,6 +7,7 @@
#include "FocusState.h"
#include "mozilla/Logging.h"
+#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/layers/APZThreadUtils.h"
static mozilla::LazyLogModule sApzFstLog("apz.focusstate");
diff --git a/gfx/layers/apz/src/InputBlockState.h b/gfx/layers/apz/src/InputBlockState.h
index d65b1cb57b..d27e7ead27 100644
--- a/gfx/layers/apz/src/InputBlockState.h
+++ b/gfx/layers/apz/src/InputBlockState.h
@@ -501,9 +501,9 @@ class TouchBlockState : public CancelableBlockState {
mIsWaitingLongTapResult = false;
}
- void SetWaitingLongTapResult() {
+ void SetWaitingLongTapResult(bool aResult) {
MOZ_ASSERT(!mForLongTap);
- mIsWaitingLongTapResult = true;
+ mIsWaitingLongTapResult = aResult;
}
bool IsWaitingLongTapResult() const { return mIsWaitingLongTapResult; }
diff --git a/gfx/layers/apz/src/InputQueue.cpp b/gfx/layers/apz/src/InputQueue.cpp
index 8ad75a9794..78b70fddf4 100644
--- a/gfx/layers/apz/src/InputQueue.cpp
+++ b/gfx/layers/apz/src/InputQueue.cpp
@@ -204,8 +204,8 @@ APZEventResult InputQueue::ReceiveTouchInput(
} else {
// If all following conditions are met, we need to wait for a content
// response (again);
- // 1) this is the first event bailing out from in-slop state after a
- // long-tap event has been fired
+ // 1) this is the first touch-move event bailing out from in-slop state
+ // after a long-tap event has been fired
// 2) there's any APZ-aware event listeners
// 3) the event block hasn't yet been prevented
//
@@ -216,7 +216,7 @@ APZEventResult InputQueue::ReceiveTouchInput(
// until a long-tap event happens, then if the user started moving their
// finger, we have to wait for a content response twice, one is for
// `touchstart` and one is for `touchmove`.
- if (wasInSlop &&
+ if (wasInSlop && aEvent.mType == MultiTouchInput::MULTITOUCH_MOVE &&
(block->WasLongTapProcessed() || block->IsWaitingLongTapResult()) &&
!block->IsTargetOriginallyConfirmed() && !block->ShouldDropEvents()) {
INPQ_LOG(
@@ -637,6 +637,12 @@ uint64_t InputQueue::InjectNewTouchBlock(AsyncPanZoomController* aTarget) {
TouchBlockState* InputQueue::StartNewTouchBlock(
const RefPtr<AsyncPanZoomController>& aTarget,
TargetConfirmationFlags aFlags) {
+ if (mPrevActiveTouchBlock && mActiveTouchBlock &&
+ mActiveTouchBlock->ForLongTap()) {
+ mPrevActiveTouchBlock->SetWaitingLongTapResult(false);
+ mPrevActiveTouchBlock = nullptr;
+ }
+
TouchBlockState* newBlock =
new TouchBlockState(aTarget, aFlags, mTouchCounter);
@@ -664,7 +670,7 @@ TouchBlockState* InputQueue::StartNewTouchBlockForLongTap(
// touch block because if the long-tap event response prevents us from
// scrolling we must stop processing any subsequent touch-move events in the
// same block.
- currentBlock->SetWaitingLongTapResult();
+ currentBlock->SetWaitingLongTapResult(true);
// We need to keep the current block alive, it will be used once after this
// new touch block for long-tap was processed.
diff --git a/gfx/layers/apz/test/gtest/TestGestureDetector.cpp b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp
index 8256667d6b..f5024e8208 100644
--- a/gfx/layers/apz/test/gtest/TestGestureDetector.cpp
+++ b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp
@@ -10,6 +10,7 @@
#include "APZCBasicTester.h"
#include "APZTestCommon.h"
#include "InputUtils.h"
+#include "apz/src/InputBlockState.h"
#include "mozilla/StaticPrefs_apz.h"
// Note: There are additional tests that test gesture detection behaviour
@@ -553,6 +554,83 @@ class APZCLongPressTester : public APZCGestureDetectorTester {
apzc->AssertStateIsReset();
}
+
+ // Tests a scenario that after a long-press event happened the original touch
+ // block initiated by a touch-start event and the touch block initiated by a
+ // long-tap event have been discarded when a new touch-start event happens.
+ void DoLongPressDiscardTouchBlockTest(bool aWithTouchMove) {
+ // Set apz.content_response_timeout > ui.click_hold_context_menus.delay and
+ // apz.touch_start_tolerance explicitly to match Android preferences.
+ SCOPED_GFX_PREF_INT("apz.content_response_timeout", 60);
+ SCOPED_GFX_PREF_INT("ui.click_hold_context_menus.delay", 30);
+ SCOPED_GFX_PREF_FLOAT("apz.touch_start_tolerance", 0.06);
+
+ MockFunction<void(std::string checkPointName)> check;
+ {
+ InSequence s;
+ EXPECT_CALL(check, Call("pre long-tap dispatch"));
+ EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10),
+ 0, apzc->GetGuid(), _, _))
+ .Times(1);
+ EXPECT_CALL(check, Call("post long-tap dispatch"));
+
+ // If a touch-move happens while long-tap is happening, there's no
+ // eLongTapUp event.
+ if (!aWithTouchMove) {
+ EXPECT_CALL(*mcc,
+ HandleTap(TapType::eLongTapUp, LayoutDevicePoint(10, 20), 0,
+ apzc->GetGuid(), _, _))
+ .Times(1);
+ }
+ EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10),
+ 0, apzc->GetGuid(), _, _))
+ .Times(1);
+ }
+
+ // Keep touching for a while to trigger a long tap event.
+ uint64_t firstTouchBlockId =
+ TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()).mInputBlockId;
+ TouchBlockState* firstTouchBlock =
+ tm->GetInputQueue()->GetCurrentTouchBlock();
+ EXPECT_NE(firstTouchBlock, nullptr);
+ EXPECT_EQ(tm->GetInputQueue()->GetBlockForId(firstTouchBlockId),
+ firstTouchBlock);
+
+ // Wait for a long tap.
+ check.Call("pre long-tap dispatch");
+ mcc->AdvanceByMillis(30);
+ check.Call("post long-tap dispatch");
+
+ // Now the current touch block is not the first touch block, it should be
+ // a new touch block for the long tap event.
+ TouchBlockState* secondTouchBlock =
+ tm->GetInputQueue()->GetCurrentTouchBlock();
+ EXPECT_NE(secondTouchBlock, firstTouchBlock);
+ EXPECT_TRUE(secondTouchBlock->ForLongTap());
+ uint64_t secondTouchBlockId = secondTouchBlock->GetBlockId();
+
+ if (aWithTouchMove) {
+ mcc->AdvanceByMillis(10);
+ TouchMove(apzc, ScreenIntPoint(10, 20), mcc->Time());
+ }
+
+ // Finish the first touch block.
+ mcc->AdvanceByMillis(10);
+ TouchUp(apzc, ScreenIntPoint(10, 20), mcc->Time());
+
+ // And start a new touch block.
+ mcc->AdvanceByMillis(10);
+ uint64_t newTouchBlockId =
+ TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()).mInputBlockId;
+
+ mcc->AdvanceByMillis(10);
+ // Now the original touch block and the touch block for long-tap should have
+ // been discarded from the input queue.
+ EXPECT_EQ(tm->GetInputQueue()->GetBlockForId(firstTouchBlockId), nullptr);
+ EXPECT_EQ(tm->GetInputQueue()->GetBlockForId(secondTouchBlockId), nullptr);
+ EXPECT_EQ(tm->GetInputQueue()->GetBlockForId(newTouchBlockId),
+ tm->GetInputQueue()->GetCurrentBlock());
+ }
};
TEST_F(APZCLongPressTester, LongPress) {
@@ -563,6 +641,28 @@ TEST_F(APZCLongPressTester, LongPressPreventDefault) {
DoLongPressPreventDefaultTest(kDefaultTouchBehavior);
}
+TEST_F(APZCLongPressTester, LongPressDiscardBlock) {
+ DoLongPressDiscardTouchBlockTest(true /* with touch-move */);
+}
+
+// Similar to above LongPressDiscardBlock but APZ is waiting for responses from
+// the content.
+TEST_F(APZCLongPressTester, LongPressDiscardBlock2) {
+ MakeApzcWaitForMainThread();
+ DoLongPressDiscardTouchBlockTest(true /* with touch-move */);
+}
+
+// Similar to above LongPressDiscardBlock/LongPressDiscardBlock2 without
+// touch-move events.
+TEST_F(APZCLongPressTester, LongPressDiscardBlock3) {
+ DoLongPressDiscardTouchBlockTest(false /* without touch-move */);
+}
+
+TEST_F(APZCLongPressTester, LongPressDiscardBlock4) {
+ MakeApzcWaitForMainThread();
+ DoLongPressDiscardTouchBlockTest(false /* without touch-move */);
+}
+
TEST_F(APZCGestureDetectorTester, DoubleTap) {
MakeApzcWaitForMainThread();
MakeApzcZoomable();
diff --git a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_textarea.html b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_textarea.html
index 99616d9834..762e1cef69 100644
--- a/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_textarea.html
+++ b/gfx/layers/apz/test/mochitest/helper_doubletap_zoom_textarea.html
@@ -7,6 +7,12 @@
<script src="apz_test_native_event_utils.js"></script>
<script src="apz_test_utils.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
+ <style>
+ #target {
+ width: 100px;
+ height: 100px;
+ }
+ </style>
<script>
async function test() {
diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_over_subframe.html b/gfx/layers/apz/test/mochitest/helper_scroll_over_subframe.html
new file mode 100644
index 0000000000..21efcbd9d6
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_scroll_over_subframe.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<html>
+ <title>A scroll over an iframe should not terminate the wheel transaction</title>
+ <script type="application/javascript" src="apz_test_utils.js"></script>
+ <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+<head>
+<style>
+body {
+ height: 250vh;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+#spacer {
+ height: 50px;
+ width: 100vw;
+ background: yellow;
+}
+
+#subframe {
+ width: 80vw;
+ height: 60vh;
+}
+</style>
+</head>
+<body>
+ <div id="spacer"></div>
+ <iframe id="subframe">
+ </iframe>
+</body>
+<script>
+const searchParams = new URLSearchParams(location.search);
+
+async function scrollWithPan() {
+ await NativePanHandler.promiseNativePanEvent(
+ document.scrollingElement,
+ 50,
+ 30,
+ 0,
+ NativePanHandler.delta,
+ NativePanHandler.beginPhase,
+ );
+
+ await NativePanHandler.promiseNativePanEvent(
+ document.scrollingElement,
+ 50,
+ 30,
+ 0,
+ NativePanHandler.delta,
+ NativePanHandler.updatePhase,
+ );
+
+ await NativePanHandler.promiseNativePanEvent(
+ document.scrollingElement,
+ 50,
+ 30,
+ 0,
+ NativePanHandler.delta,
+ NativePanHandler.endPhase,
+ );
+}
+
+async function scrollWithWheel() {
+ await promiseMoveMouseAndScrollWheelOver(document.scrollingElement, 50, 30,
+ false, 100);
+}
+
+async function test() {
+ let iframeURL =
+ SimpleTest.getTestFileURL("helper_scroll_over_subframe_child.html");
+
+ switch (searchParams.get("oop")) {
+ case "true":
+ iframeURL = iframeURL.replace(window.location.origin, "https://example.com/");
+ break;
+ default:
+ break;
+ }
+
+ const iframeLoadPromise = promiseOneEvent(subframe, "load", null);
+ subframe.src = iframeURL;
+ await iframeLoadPromise;
+
+ await SpecialPowers.spawn(subframe, [], async () => {
+ await content.wrappedJSObject.waitUntilApzStable();
+ await SpecialPowers.contentTransformsReceived(content);
+ });
+
+ let childWindowReceivedWheelEvent = false;
+
+ window.addEventListener("message", e => {
+ if (e.data == "child-received-wheel-event") {
+ childWindowReceivedWheelEvent = true;
+ }
+ });
+
+ await SpecialPowers.spawn(subframe, [], () => {
+ let target = content.document.getElementById("target")
+ target.style.backgroundColor = "green";
+ content.getComputedStyle(target).backgroundColor;
+ target.addEventListener("wheel", () => {
+ target.style.backgroundColor = "red";
+ content.getComputedStyle(target).backgroundColor;
+ });
+ return new Promise(resolve => resolve());
+ });
+
+ await promiseFrame();
+
+ let transformEndPromise = promiseTransformEnd();
+
+ // Scroll over the iframe
+ switch (searchParams.get("scroll")) {
+ case "wheel":
+ await scrollWithWheel();
+ break;
+ case "pan":
+ await scrollWithPan();
+ break;
+ default:
+ ok(false, "Unsupported scroll value: " + searchParams.get("scroll"));
+ break;
+ }
+
+ await transformEndPromise;
+
+ // Wait an extra frame to ensure any message from the child has
+ // extra time to be sent to the parent.
+ await promiseFrame();
+
+ let res = await SpecialPowers.spawn(subframe, [], () => {
+ let target = content.document.getElementById("target")
+ return target.style.backgroundColor;
+ });
+
+ await promiseFrame();
+
+ // We should not have fired a wheel event to the element in the iframe
+ ok(!childWindowReceivedWheelEvent, "Child window should not receive wheel events");
+ is(res, "green", "OOP iframe does not halt user scroll of parent");
+}
+waitUntilApzStable().then(test).then(subtestDone, subtestFailed);
+</script>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_over_subframe_child.html b/gfx/layers/apz/test/mochitest/helper_scroll_over_subframe_child.html
new file mode 100644
index 0000000000..48f03a51d9
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_scroll_over_subframe_child.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<script src="apz_test_utils.js"></script>
+<style>
+body {
+ margin: 0px;
+}
+
+#target {
+ width: 100%;
+ height: 100vh;
+ background: blue;
+ overflow: scroll;
+}
+</style>
+</head>
+<body>
+<div id="target">
+</div>
+</body>
+<script>
+ window.addEventListener("wheel", () => {
+ window.parent.postMessage("child-received-wheel-event", "*");
+ });
+</script>
+</html>
diff --git a/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html b/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html
index fe4a0784a9..c1e0351ccb 100644
--- a/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html
+++ b/gfx/layers/apz/test/mochitest/test_group_double_tap_zoom.html
@@ -24,12 +24,12 @@ var logging_and_doubletap_prefs = [
var subtests = [
{"file": "helper_doubletap_zoom.html", "prefs": doubletap_prefs},
{"file": "helper_doubletap_zoom_img.html", "prefs": doubletap_prefs},
- {"file": "helper_doubletap_zoom_textarea.html", "prefs": doubletap_prefs},
{"file": "helper_doubletap_zoom_horizontal_center.html", "prefs": doubletap_prefs},
{"file": "helper_doubletap_zoom_bug1702464.html", "prefs": doubletap_prefs},
{"file": "helper_doubletap_zoom_large_overflow.html", "prefs": doubletap_prefs},
{"file": "helper_doubletap_zoom_fixedpos.html", "prefs": logging_and_doubletap_prefs},
{"file": "helper_doubletap_zoom_tallwide.html", "prefs": doubletap_prefs},
+ {"file": "helper_doubletap_zoom_textarea.html", "prefs": doubletap_prefs},
];
if (getPlatform() == "mac") {
diff --git a/gfx/layers/apz/test/mochitest/test_group_pointerevents.html b/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
index 9ec03edd59..8bc0690bfc 100644
--- a/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
+++ b/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
@@ -24,14 +24,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1285070
{"file": "helper_bug1682170_pointercancel_on_touchaction_pinchzoom.html",
"prefs": touch_action_prefs},
{"file": "helper_bug1719855_pointercancel_on_touchmove_after_contextmenu_prevented.html"},
+ {"file": "helper_bug1285070.html"},
+ {"file": "helper_bug1299195.html", "prefs": [["dom.meta-viewport.enabled", isMac]]},
];
+
if (getPlatform() != "android") {
// Bug 1858610: these subtests are flaky on Android.
- subtests.push(
- {"file": "helper_bug1285070.html"},
- {"file": "helper_bug1299195.html", "prefs": [["dom.meta-viewport.enabled", isMac]]},
- {"file": "helper_bug1502010_unconsumed_pan.html"}
- )
+ subtests.push({"file": "helper_bug1502010_unconsumed_pan.html"});
}
if (isApzEnabled()) {
diff --git a/gfx/layers/apz/test/mochitest/test_group_wheelevents.html b/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
index 42ce15a247..93746b9f6f 100644
--- a/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
+++ b/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
@@ -20,6 +20,13 @@ var prefs = [
["mousewheel.transaction.timeout", 0],
];
+var wheel_transaction_prefs = [
+ ["dom.event.wheel-event-groups.enabled", true],
+ ["mousewheel.transaction.timeout", 10000],
+ ["apz.test.mac.synth_wheel_input", true],
+ ...getSmoothScrollPrefs("wheel"),
+];
+
// For helper_scroll_over_scrollbar, we need to set a pref to force
// layerization of the scrollbar track to reproduce the bug being fixed.
// Otherwise, the bug only manifests with overlay scrollbars on macOS,
@@ -48,6 +55,10 @@ var subtests = [
prefs: [["general.smoothScroll", false],
["apz.test.mac.synth_wheel_input", true]]},
{"file": "helper_scroll_anchoring_on_wheel.html", prefs: smoothness_prefs},
+ {"file": "helper_scroll_over_subframe.html?scroll=wheel", prefs: wheel_transaction_prefs},
+ {"file": "helper_scroll_over_subframe.html?oop=true&scroll=wheel", prefs: wheel_transaction_prefs},
+ {"file": "helper_scroll_over_subframe.html?scroll=pan", prefs: wheel_transaction_prefs},
+ {"file": "helper_scroll_over_subframe.html?oop=true&scroll=pan", prefs: wheel_transaction_prefs},
];
subtests.push(...buildRelativeScrollSmoothnessVariants("wheel", ["scrollBy", "scrollTo", "scrollTop"]));