Commit 2a24897a authored by Dmitry Gozman's avatar Dmitry Gozman Committed by Commit Bot

DevTools protocol: introduce DOM.scrollIntoViewIfNeeded

This method allows to scroll specific rect into view if needed and
ensure that no scrolling animations run. This ability is required
for browser automation, for example to test user interaction with
specific region of the element.

Bug: 1049173

Change-Id: I7163d1cfd6ae17a676c64727ab3151d0300a6ec0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2037847
Commit-Queue: Dmitry Gozman <dgozman@chromium.org>
Reviewed-by: default avatarPavel Feldman <pfeldman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#738973}
parent 9b86dc2d
...@@ -1668,6 +1668,19 @@ domain DOM ...@@ -1668,6 +1668,19 @@ domain DOM
# Node description. # Node description.
Node node Node node
# Scrolls the specified rect of the given node into view if not already visible.
experimental command scrollIntoViewIfNeeded
parameters
# Identifier of the node.
optional NodeId nodeId
# Identifier of the backend node.
optional BackendNodeId backendNodeId
# JavaScript object id of the node wrapper.
optional Runtime.RemoteObjectId objectId
# The rect to be scrolled into view, relative to the node's border box, in CSS pixels.
# When omitted, center of the node will be used, similar to Element.scrollIntoView.
optional Rect rect
# Disables DOM agent for the given page. # Disables DOM agent for the given page.
command disable command disable
......
...@@ -84,6 +84,7 @@ ...@@ -84,6 +84,7 @@
#include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/page/frame_tree.h" #include "third_party/blink/renderer/core/page/frame_tree.h"
#include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/scroll/scroll_into_view_params_type_converters.h"
#include "third_party/blink/renderer/core/svg/svg_svg_element.h" #include "third_party/blink/renderer/core/svg/svg_svg_element.h"
#include "third_party/blink/renderer/core/xml/document_xpath_evaluator.h" #include "third_party/blink/renderer/core/xml/document_xpath_evaluator.h"
#include "third_party/blink/renderer/core/xml/xpath_result.h" #include "third_party/blink/renderer/core/xml/xpath_result.h"
...@@ -2273,6 +2274,46 @@ protocol::Response InspectorDOMAgent::describeNode( ...@@ -2273,6 +2274,46 @@ protocol::Response InspectorDOMAgent::describeNode(
return Response::OK(); return Response::OK();
} }
protocol::Response InspectorDOMAgent::scrollIntoViewIfNeeded(
protocol::Maybe<int> node_id,
protocol::Maybe<int> backend_node_id,
protocol::Maybe<String> object_id,
protocol::Maybe<protocol::DOM::Rect> rect) {
Node* node = nullptr;
Response response = AssertNode(node_id, backend_node_id, object_id, node);
if (!response.isSuccess())
return response;
if (!node)
return Response::Error("Node not found");
node->GetDocument().EnsurePaintLocationDataValidForNode(
node, DocumentUpdateReason::kInspector);
if (!node->isConnected())
return Response::Error("Node is detached from document");
LayoutObject* layout_object = node->GetLayoutObject();
if (!layout_object)
return Response::Error("Node does not have a layout object");
PhysicalRect rect_to_scroll = PhysicalRect::EnclosingRect(
layout_object->AbsoluteBoundingBoxFloatRect());
if (rect.isJust()) {
rect_to_scroll.SetX(rect_to_scroll.X() +
LayoutUnit(rect.fromJust()->getX()));
rect_to_scroll.SetY(rect_to_scroll.Y() +
LayoutUnit(rect.fromJust()->getY()));
rect_to_scroll.SetWidth(LayoutUnit(rect.fromJust()->getWidth()));
rect_to_scroll.SetHeight(LayoutUnit(rect.fromJust()->getHeight()));
}
layout_object->ScrollRectToVisible(
rect_to_scroll,
CreateScrollIntoViewParams(
ScrollAlignment::kAlignCenterIfNeeded,
ScrollAlignment::kAlignCenterIfNeeded,
mojom::blink::ScrollIntoViewParams::Type::kProgrammatic,
true /* make_visible_in_visual_viewport */,
mojom::blink::ScrollIntoViewParams::Behavior::kInstant,
true /* is_for_scroll_sequence */, false /* zoom_into_rect */));
return Response::OK();
}
protocol::Response InspectorDOMAgent::getFrameOwner( protocol::Response InspectorDOMAgent::getFrameOwner(
const String& frame_id, const String& frame_id,
int* backend_node_id, int* backend_node_id,
......
...@@ -234,6 +234,11 @@ class CORE_EXPORT InspectorDOMAgent final ...@@ -234,6 +234,11 @@ class CORE_EXPORT InspectorDOMAgent final
protocol::Maybe<int> depth, protocol::Maybe<int> depth,
protocol::Maybe<bool> pierce, protocol::Maybe<bool> pierce,
std::unique_ptr<protocol::DOM::Node>*) override; std::unique_ptr<protocol::DOM::Node>*) override;
protocol::Response scrollIntoViewIfNeeded(
protocol::Maybe<int> node_id,
protocol::Maybe<int> backend_node_id,
protocol::Maybe<String> object_id,
protocol::Maybe<protocol::DOM::Rect> rect) override;
protocol::Response getFrameOwner(const String& frame_id, protocol::Response getFrameOwner(const String& frame_id,
int* backend_node_id, int* backend_node_id,
......
Tests DOM.scrollIntoViewIfNeeded.
0x0
450x850
450x2150
202x1902
600x2300
423x2234
(async function(testRunner) {
var {page, session, dp} = await testRunner.startHTML(
`
<style>
body {
margin: 0;
width: 800px;
height: 800px;
}
.container {
width: 200px;
height: 200px;
overflow: auto;
scroll-behavior: smooth;
border: 1px solid black;
}
.container::-webkit-scrollbar {
width: 0;
height: 0;
}
.child {
width: 500px;
height: 500px;
margin-top: 700px;
margin-left: 300px;
background: red;
}
</style>
<script>
function getScroll() {
const c = document.querySelector('.container');
return c.scrollLeft + 'x' + c.scrollTop;
}
</script>
<div class=container>
<div class=child>
</div>
</div>
`,
'Tests DOM.scrollIntoViewIfNeeded.');
const response = await dp.Runtime.evaluate(
{expression: `document.querySelector('.child')`});
const objectId = response.result.result.objectId;
testRunner.log(await session.evaluate(`getScroll()`));
// Do not await scrollIntoViewIfNeeded to ensure we got synchronous scrolling.
dp.DOM.scrollIntoViewIfNeeded({objectId});
testRunner.log(await session.evaluate(`getScroll()`));
// Ensure that we update layout before scrolling.
session.evaluate(
`document.querySelector('.child').style.marginTop = '2000px'`);
dp.DOM.scrollIntoViewIfNeeded({objectId});
testRunner.log(await session.evaluate(`getScroll()`));
// Top-left corner should be visible.
dp.DOM.scrollIntoViewIfNeeded(
{objectId, rect: {x: 1, y: 1, width: 0, height: 0}});
testRunner.log(await session.evaluate(`getScroll()`));
// Almost bottom-right corner should be visible.
dp.DOM.scrollIntoViewIfNeeded(
{objectId, rect: {x: 490, y: 480, width: 5, height: 7}});
testRunner.log(await session.evaluate(`getScroll()`));
// Specific 200x200 rect should be visible.
dp.DOM.scrollIntoViewIfNeeded(
{objectId, rect: {x: 123, y: 234, width: 200, height: 200}});
testRunner.log(await session.evaluate(`getScroll()`));
testRunner.completeTest();
})
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment