Implement document.elementsFromPoint
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 26 Jul 2017 20:51:26 +0000 (20:51 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 26 Jul 2017 20:51:26 +0000 (20:51 +0000)
https://bugs.webkit.org/show_bug.cgi?id=153137

Patch by Ali Juma <ajuma@chromium.org> on 2017-07-26
Reviewed by Simon Fraser.

LayoutTests/imported/w3c:

Add tests from upstream pull request https://github.com/w3c/web-platform-tests/pull/6568.

* web-platform-tests/cssom-view/elementsFromPoint-expected.txt: Added.
* web-platform-tests/cssom-view/elementsFromPoint-iframes-expected.txt: Added.
* web-platform-tests/cssom-view/elementsFromPoint-iframes.html: Added.
* web-platform-tests/cssom-view/elementsFromPoint-invalid-cases-expected.txt: Added.
* web-platform-tests/cssom-view/elementsFromPoint-invalid-cases.html: Added.
* web-platform-tests/cssom-view/elementsFromPoint-shadowroot-expected.txt: Added.
* web-platform-tests/cssom-view/elementsFromPoint-shadowroot.html: Added.
* web-platform-tests/cssom-view/elementsFromPoint-simple-expected.txt: Added.
* web-platform-tests/cssom-view/elementsFromPoint-simple.html: Added.
* web-platform-tests/cssom-view/elementsFromPoint-svg-expected.txt: Added.
* web-platform-tests/cssom-view/elementsFromPoint-svg.html: Added.
* web-platform-tests/cssom-view/elementsFromPoint-table-expected.txt: Added.
* web-platform-tests/cssom-view/elementsFromPoint-table.html: Added.
* web-platform-tests/cssom-view/negativeMargins-expected.txt:
* web-platform-tests/cssom-view/resources/elementsFromPoint.js: Added.
(nodeToString.prototype.else):
(nodeListToString):
(assertElementsFromPoint):
(checkElementsFromPointFourCorners):
* web-platform-tests/cssom-view/resources/iframe1.html: Added.
* web-platform-tests/cssom-view/resources/iframe2.html: Added.

Source/WebCore:

This ports Blink's implementation of elementsFromPoint, from the
following patches by Philip Rogers (pdr@chromium.org):
-https://src.chromium.org/viewvc/blink?revision=190686&view=revision
-https://src.chromium.org/viewvc/blink?revision=191240&view=revision
-https://src.chromium.org/viewvc/blink?revision=199214&view=revision

Tests: imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-iframes.html
       imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-invalid-cases.html
       imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-shadowroot.html
       imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-simple.html
       imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-svg.html
       imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-table.html

* dom/DocumentOrShadowRoot.idl:
* dom/TreeScope.cpp:
(WebCore::absolutePointIfNotClipped):
(WebCore::TreeScope::nodeFromPoint):
(WebCore::TreeScope::elementFromPoint):
(WebCore::TreeScope::elementsFromPoint):
* dom/TreeScope.h:
* page/EventHandler.cpp:
(WebCore::EventHandler::hitTestResultAtPoint):
* rendering/EllipsisBox.cpp:
(WebCore::EllipsisBox::nodeAtPoint):
* rendering/HitTestRequest.h:
(WebCore::HitTestRequest::HitTestRequest):
(WebCore::HitTestRequest::resultIsElementList):
(WebCore::HitTestRequest::includesAllElementsUnderPoint):
* rendering/HitTestResult.cpp:
(WebCore::HitTestResult::HitTestResult):
(WebCore::HitTestResult::operator=):
(WebCore::HitTestResult::addNodeToListBasedTestResult):
(WebCore::HitTestResult::append):
(WebCore::HitTestResult::listBasedTestResult):
(WebCore::HitTestResult::mutableListBasedTestResult):
(WebCore::HitTestResult::addNodeToRectBasedTestResult): Deleted.
(WebCore::HitTestResult::rectBasedTestResult): Deleted.
(WebCore::HitTestResult::mutableRectBasedTestResult): Deleted.
* rendering/HitTestResult.h:
* rendering/InlineFlowBox.cpp:
(WebCore::InlineFlowBox::nodeAtPoint):
* rendering/InlineTextBox.cpp:
(WebCore::InlineTextBox::nodeAtPoint):
* rendering/RenderBlock.cpp:
(WebCore::RenderBlock::nodeAtPoint):
* rendering/RenderBox.cpp:
(WebCore::RenderBox::nodeAtPoint):
* rendering/RenderImage.cpp:
(WebCore::RenderImage::nodeAtPoint):
* rendering/RenderInline.cpp:
(WebCore::RenderInline::hitTestCulledInline):
* rendering/RenderLayer.cpp:
(WebCore::RenderLayer::hitTestFixedLayersInNamedFlows):
(WebCore::RenderLayer::hitTestLayer):
(WebCore::RenderLayer::hitTestContents):
(WebCore::RenderLayer::hitTestList):
(WebCore::RenderLayer::calculateClipRects):
* rendering/RenderTable.cpp:
(WebCore::RenderTable::nodeAtPoint):
* rendering/RenderTableSection.cpp:
(WebCore::RenderTableSection::nodeAtPoint):
* rendering/RenderWidget.cpp:
(WebCore::RenderWidget::nodeAtPoint):
* rendering/SimpleLineLayoutFunctions.cpp:
(WebCore::SimpleLineLayout::hitTestFlow):
* rendering/svg/RenderSVGContainer.cpp:
(WebCore::RenderSVGContainer::nodeAtFloatPoint):
* rendering/svg/RenderSVGImage.cpp:
(WebCore::RenderSVGImage::nodeAtFloatPoint):
* rendering/svg/RenderSVGRoot.cpp:
(WebCore::RenderSVGRoot::nodeAtPoint):
* rendering/svg/RenderSVGShape.cpp:
(WebCore::RenderSVGShape::nodeAtFloatPoint):
* rendering/svg/SVGInlineTextBox.cpp:
(WebCore::SVGInlineTextBox::nodeAtPoint):
* testing/Internals.cpp:
(WebCore::Internals::nodesFromRect):

LayoutTests:

* TestExpectations: Unskip a test.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@219961 268f45cc-cd09-0410-ab3c-d52691b4dbfc

46 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-iframes-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-iframes.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-invalid-cases-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-invalid-cases.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-shadowroot-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-shadowroot.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-simple-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-simple.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-svg-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-svg.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-table-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-table.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/negativeMargins-expected.txt
LayoutTests/imported/w3c/web-platform-tests/cssom-view/resources/elementsFromPoint.js [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/resources/iframe1.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/cssom-view/resources/iframe2.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/dom/DocumentOrShadowRoot.idl
Source/WebCore/dom/TreeScope.cpp
Source/WebCore/dom/TreeScope.h
Source/WebCore/page/EventHandler.cpp
Source/WebCore/rendering/EllipsisBox.cpp
Source/WebCore/rendering/HitTestRequest.h
Source/WebCore/rendering/HitTestResult.cpp
Source/WebCore/rendering/HitTestResult.h
Source/WebCore/rendering/InlineFlowBox.cpp
Source/WebCore/rendering/InlineTextBox.cpp
Source/WebCore/rendering/RenderBlock.cpp
Source/WebCore/rendering/RenderBox.cpp
Source/WebCore/rendering/RenderImage.cpp
Source/WebCore/rendering/RenderInline.cpp
Source/WebCore/rendering/RenderLayer.cpp
Source/WebCore/rendering/RenderTable.cpp
Source/WebCore/rendering/RenderTableSection.cpp
Source/WebCore/rendering/RenderWidget.cpp
Source/WebCore/rendering/SimpleLineLayoutFunctions.cpp
Source/WebCore/rendering/svg/RenderSVGContainer.cpp
Source/WebCore/rendering/svg/RenderSVGImage.cpp
Source/WebCore/rendering/svg/RenderSVGRoot.cpp
Source/WebCore/rendering/svg/RenderSVGShape.cpp
Source/WebCore/rendering/svg/SVGInlineTextBox.cpp
Source/WebCore/testing/Internals.cpp

index 5f2ff93ec09aacfaccbc112575fc22108d6c02ff..5ced66f8f3401d3235d2d6e105520404998473bb 100644 (file)
@@ -1,3 +1,12 @@
+2017-07-26  Ali Juma  <ajuma@chromium.org>
+
+        Implement document.elementsFromPoint
+        https://bugs.webkit.org/show_bug.cgi?id=153137
+
+        Reviewed by Simon Fraser.
+
+        * TestExpectations: Unskip a test.
+
 2017-07-26  Brian Burg  <bburg@apple.com>
 
         Remove WEB_TIMING feature flag
index 30bfd097ce140426d7a9deaf03d175f6a77916e8..39781e873b2176fa83f2cdce1c154e607d40fcbb 100644 (file)
@@ -1347,7 +1347,6 @@ fast/table/colspanMinWidth-vertical.html [ Skip ]
 fast/forms/range/range-remove-on-drag.html [ Skip ]
 
 # CSSOM View module
-webkit.org/b/153137 imported/w3c/web-platform-tests/cssom-view/elementsFromPoint.html [ Skip ]
 webkit.org/b/5991 imported/w3c/web-platform-tests/cssom-view/scrollingElement.html [ Skip ]
 
 # FileAPI
index 0c7ce787c16429fbba66404663c26a9b36e4772d..9fdeccb7982ba9e37f9b9f9c9d118181b713fc43 100644 (file)
@@ -1,3 +1,34 @@
+2017-07-26  Ali Juma  <ajuma@chromium.org>
+
+        Implement document.elementsFromPoint
+        https://bugs.webkit.org/show_bug.cgi?id=153137
+
+        Reviewed by Simon Fraser.
+
+        Add tests from upstream pull request https://github.com/w3c/web-platform-tests/pull/6568.
+
+        * web-platform-tests/cssom-view/elementsFromPoint-expected.txt: Added.
+        * web-platform-tests/cssom-view/elementsFromPoint-iframes-expected.txt: Added.
+        * web-platform-tests/cssom-view/elementsFromPoint-iframes.html: Added.
+        * web-platform-tests/cssom-view/elementsFromPoint-invalid-cases-expected.txt: Added.
+        * web-platform-tests/cssom-view/elementsFromPoint-invalid-cases.html: Added.
+        * web-platform-tests/cssom-view/elementsFromPoint-shadowroot-expected.txt: Added.
+        * web-platform-tests/cssom-view/elementsFromPoint-shadowroot.html: Added.
+        * web-platform-tests/cssom-view/elementsFromPoint-simple-expected.txt: Added.
+        * web-platform-tests/cssom-view/elementsFromPoint-simple.html: Added.
+        * web-platform-tests/cssom-view/elementsFromPoint-svg-expected.txt: Added.
+        * web-platform-tests/cssom-view/elementsFromPoint-svg.html: Added.
+        * web-platform-tests/cssom-view/elementsFromPoint-table-expected.txt: Added.
+        * web-platform-tests/cssom-view/elementsFromPoint-table.html: Added.
+        * web-platform-tests/cssom-view/negativeMargins-expected.txt:
+        * web-platform-tests/cssom-view/resources/elementsFromPoint.js: Added.
+        (nodeToString.prototype.else):
+        (nodeListToString):
+        (assertElementsFromPoint):
+        (checkElementsFromPointFourCorners):
+        * web-platform-tests/cssom-view/resources/iframe1.html: Added.
+        * web-platform-tests/cssom-view/resources/iframe2.html: Added.
+
 2017-07-11  Frederic Wang  <fwang@igalia.com>
 
         Add attribute allow-top-navigation-by-user-activation to iframe sandbox
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-expected.txt
new file mode 100644 (file)
index 0000000..d66e8dc
--- /dev/null
@@ -0,0 +1,17 @@
+    
+Hello!
+Another teal
+
+PASS Negative co-ordinates 
+PASS co-ordinates larger than the viewport 
+PASS co-ordinates larger than the viewport from in iframe 
+PASS Return first element that is the target for hit testing 
+PASS First element to get mouse events with pointer-events css 
+PASS SVG element at x,y 
+PASS transformed element at x,y 
+PASS no hit target at x,y 
+PASS No viewport available 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-iframes-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-iframes-expected.txt
new file mode 100644 (file)
index 0000000..886f6bf
--- /dev/null
@@ -0,0 +1,5 @@
+
+PASS elementsFromPoint on the root document for points in iframe elements 
+PASS elementsFromPoint on inner documents 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-iframes.html b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-iframes.html
new file mode 100644 (file)
index 0000000..3bba161
--- /dev/null
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/elementsFromPoint.js"></script>
+<script>
+var loadedFrameCount = 0;
+var t1 = async_test('elementsFromPoint on the root document for points in iframe elements');
+var t2 = async_test('elementsFromPoint on inner documents');
+
+function onFrameLoaded() {
+    loadedFrameCount++;
+    if (loadedFrameCount < 2)
+        return;
+
+    var body = document.body;
+    var html = document.documentElement;
+    var iframe = document.getElementById('iframe');
+    var scrollableIframe = document.getElementById('scrollableIframe');
+    t1.step(function() {
+        checkElementsFromPointFourCorners('document', 'iframe',
+            [iframe, body,  html],
+            [iframe, body, html],
+            [iframe, body, html],
+            [scrollableIframe, iframe, body, html]);
+
+        checkElementsFromPointFourCorners('document', 'scrollableIframe',
+            [scrollableIframe, iframe, body, html],
+            [scrollableIframe, iframe, body, html],
+            [scrollableIframe, iframe, body, html],
+            [scrollableIframe, iframe, body, html]);
+    });
+    t1.done();
+
+    t2.step(function() {
+        var iframeDocument = document.getElementById('iframe').contentDocument;
+        var iframeRoot = iframeDocument.documentElement;
+        var iframeBody = iframeDocument.body;
+        var iframeDiv = iframeDocument.getElementById('div');
+        checkElementsFromPointFourCorners('document.getElementById(\'iframe\').contentDocument', 'div',
+            [iframeDiv, iframeBody, iframeRoot],
+            [iframeDiv, iframeBody, iframeRoot],
+            [iframeDiv, iframeBody, iframeRoot],
+            [iframeDiv, iframeBody, iframeRoot]);
+
+        var iframeDocument2 = document.getElementById('scrollableIframe').contentDocument;
+        var iframeRoot2 = iframeDocument2.documentElement;
+        var iframeBody2 = iframeDocument2.body;
+        var iframeSmallDiv = iframeDocument2.getElementById('small');
+        var iframeBigDiv = iframeDocument2.getElementById('big');
+        checkElementsFromPointFourCorners('document.getElementById(\'scrollableIframe\').contentDocument', 'big',
+            [iframeSmallDiv, iframeBigDiv, iframeBody2, iframeRoot2],
+            [iframeBigDiv, iframeBody2, iframeRoot2],
+            [],
+            []);
+    });
+    t2.done();
+}
+</script>
+<style>
+html, body {
+    margin: 0;
+    padding: 0;
+}
+body {
+    height: 500px;
+}
+#iframe {
+    width: 200px;
+    height: 200px;
+}
+#scrollableIframe {
+    position: absolute;
+    top: 0;
+    left: 0;
+    transform: translate(50px, 50px);
+    width: 150px;
+    height: 150px;
+    overflow-y: scroll;
+    overflow-x: scroll;
+}
+</style>
+<iframe id="iframe" src="resources/iframe1.html"></iframe>
+<iframe id="scrollableIframe" src="resources/iframe2.html"></iframe>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-invalid-cases-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-invalid-cases-expected.txt
new file mode 100644 (file)
index 0000000..efef29b
--- /dev/null
@@ -0,0 +1,5 @@
+
+PASS The root element is the last element returned for otherwise empty queries within the viewport 
+PASS The root element is the last element returned for valid queries 
+PASS An empty sequence is returned for queries outside the viewport 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-invalid-cases.html b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-invalid-cases.html
new file mode 100644 (file)
index 0000000..369cffc
--- /dev/null
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/elementsFromPoint.js"></script>
+<style>
+html {
+    overflow-y: scroll;
+    overflow-x: scroll;
+}
+html, body {
+    margin: 0;
+    padding: 0;
+}
+body {
+    width: 100%;
+    height: 100%;
+}
+#simpleDiv {
+    width: 200px;
+    height: 200px;
+    background-color: rgba(0,255,0,0.5);
+}
+#beyondTopLeft {
+    position: absolute;
+    transform: translate3d(-100px, -100px, 10px);
+    left: 0;
+    top: 0;
+    width: 100px;
+    height: 100px;
+    background-color: rgba(0,0,0,0.1);
+}
+</style>
+<body>
+<div id="beyondTopLeft"></div>
+<div id="simpleDiv"></div>
+<script>
+test(function() {
+    assertElementsFromPoint('document', 300, 300, [document.documentElement]);
+}, "The root element is the last element returned for otherwise empty queries within the viewport");
+
+test(function() {
+    var simpleDiv = document.getElementById('simpleDiv');
+    var simpleRect = simpleDiv.getBoundingClientRect();
+    var simpleCoords = (simpleRect.right - 1) + ', ' + (simpleRect.bottom - 1);
+    assertElementsFromPoint('document', simpleRect.right - 1, simpleRect.bottom - 1,
+        [simpleDiv, document.body, document.documentElement]);
+}, "The root element is the last element returned for valid queries");
+
+test(function() {
+    assertElementsFromPoint('document', window.innerWidth + 1, window.innerHeight + 1, []);
+    assertElementsFromPoint('document', -1, -1, []);
+    assertElementsFromPoint('document', 1, -1, []);
+    assertElementsFromPoint('document', -1, 1, []);
+}, "An empty sequence is returned for queries outside the viewport");
+</script>
+</body>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-shadowroot-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-shadowroot-expected.txt
new file mode 100644 (file)
index 0000000..9875d33
--- /dev/null
@@ -0,0 +1,5 @@
+
+PASS elementsFromPoint on the document root should not return elements in shadow trees 
+PASS elementsFromPoint on a shadow root should include elements in that shadow tree 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-shadowroot.html b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-shadowroot.html
new file mode 100644 (file)
index 0000000..b3e0c6d
--- /dev/null
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/elementsFromPoint.js"></script>
+<style>
+html, body {
+    margin: 0;
+    padding: 0;
+}
+body {
+    height: 500px;
+}
+</style>
+<body>
+<div id="host"></div>
+<div id="blockHost"></div>
+<span id="inlineBlockHost" style="display:inline-block;"></span>
+<input type="submit" id="submit">
+<script>
+function assertElementsFromPoint(doc, x, y, expected) {
+  var query = doc + '.elementsFromPoint(' + x + ',' + y + ')';
+  var sequence = eval(query);
+  assert_equals(nodeListToString(sequence), nodeListToString(expected), query);
+}
+
+function createBox(id) {
+    var div = document.createElement('div');
+    div.id = id;
+    div.style.width = '100px';
+    div.style.height = '10px';
+    return div;
+}
+
+function centerX(element) {
+    return element.offsetLeft + element.offsetWidth / 2;
+}
+
+function centerY(element) {
+    return element.offsetTop + element.offsetHeight / 2;
+}
+
+var shadowRoot = host.attachShadow({mode: 'closed'});
+var box11 = createBox('box11');
+var box12 = createBox('box12');
+var box13 = createBox('box13');
+shadowRoot.appendChild(box11);
+shadowRoot.appendChild(box12);
+shadowRoot.appendChild(box13);
+
+var nestedHost = document.createElement('div');
+var nestedShadowRoot = nestedHost.attachShadow({mode: 'closed'});
+var box21 = createBox('box21');
+var box22 = createBox('box22');
+var box23 = createBox('box23');
+nestedShadowRoot.appendChild(box21);
+nestedShadowRoot.appendChild(box22);
+nestedShadowRoot.appendChild(box23);
+
+shadowRoot.appendChild(nestedHost);
+
+var x12 = centerX(box12);
+var y12 = centerY(box12);
+var x22 = centerX(box22);
+var y22 = centerY(box22);
+
+var root3 = blockHost.attachShadow({mode: 'closed'});
+root3.appendChild(document.createTextNode('text1'));
+var root4 = inlineBlockHost.attachShadow({mode: 'closed'});
+root4.appendChild(document.createTextNode('text2'));
+
+test(function() {
+    assertElementsFromPoint('document', x22, y22, [host, document.body, document.documentElement]);
+    assertElementsFromPoint('document', centerX(blockHost), centerY(blockHost),
+        [blockHost, document.body, document.documentElement]);
+    assertElementsFromPoint('document', centerX(inlineBlockHost), centerY(inlineBlockHost),
+        [inlineBlockHost, document.body, document.documentElement]);
+    assertElementsFromPoint('document', centerX(submit), centerY(submit),
+        [submit, document.body, document.documentElement]);
+}, 'elementsFromPoint on the document root should not return elements in shadow trees');
+
+test(function() {
+    assert_not_equals(shadowRoot.elementsFromPoint(x12, y12).indexOf(box12), -1);
+    assert_not_equals(shadowRoot.elementsFromPoint(x22, y22).indexOf(nestedHost), -1);
+    assert_not_equals(nestedShadowRoot.elementsFromPoint(x22, y22).indexOf(box22), -1);
+}, 'elementsFromPoint on a shadow root should include elements in that shadow tree');
+</script>
+</body>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-simple-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-simple-expected.txt
new file mode 100644 (file)
index 0000000..5d6ef83
--- /dev/null
@@ -0,0 +1,8 @@
+
+PASS elementsFromPoint for each corner of a simple div 
+PASS elementsFromPoint for each corner of a div that has a pseudo-element 
+PASS elementsFromPoint for each corner of a div that is between another div and its pseudo-element 
+PASS elementsFromPoint for each corner of a div that has a margin 
+PASS elementsFromPoint for each corner of a div with pointer-events:none 
+PASS elementsFromPoint for each corner of a div with a 3d transform 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-simple.html b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-simple.html
new file mode 100644 (file)
index 0000000..4973121
--- /dev/null
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/elementsFromPoint.js"></script>
+<style>
+html, body {
+    margin: 0;
+    padding: 0;
+}
+body {
+    height: 500px;
+}
+#simpleDiv {
+    width: 200px;
+    height: 200px;
+    background-color: rgba(0,0,255,0.5);
+}
+#divWithPseudo {
+    position: absolute;
+    left: 50px;
+    top: 50px;
+    width: 100px;
+    height: 100px;
+    background-color: rgba(255,0,0,0.5);
+}
+#divWithPseudo::before {
+    position: absolute;
+    left: 20px;
+    top: 20px;
+    width: 100px;
+    height: 100px;
+    content: "::before";
+    background-color: rgba(255,0,0,0.5);
+    z-index: 9999;
+}
+#divBetweenPseudo {
+    position: absolute;
+    left: 100px;
+    top: 100px;
+    width: 100px;
+    height: 100px;
+    background-color: rgba(0,255,0,0.5);
+}
+#withMargin {
+    margin-top: -15px;
+    width: 200px;
+    height: 200px;
+    background-color: rgba(0,0,0,0.5);
+}
+#inlineSpan {
+    float: right;
+    background-color: yellow;
+    width: 100px;
+    height: 1em;
+}
+#noPointerEvents {
+    position: absolute;
+    left: 50px;
+    top: 50px;
+    width: 100px;
+    height: 300px;
+    background-color: rgba(0,0,0,0.1);
+    pointer-events: none;
+}
+#threeD {
+    position: absolute;
+    transform: translate3d(-100px, -100px, 10px);
+    left: 140px;
+    top: 140px;
+    width: 200px;
+    height: 50px;
+    background-color: rgba(255,255,255,0.5);
+}
+</style>
+<div id="simpleDiv"></div>
+<div id="divWithPseudo"></div>
+<div id="divBetweenPseudo"></div>
+<div id="withMargin"><span id="inlineSpan"></span></div>
+<div id="noPointerEvents"></div>
+<div id="threeD"></div>
+<script>
+var body = document.body;
+var html = document.documentElement;
+test(function() {
+    checkElementsFromPointFourCorners('document', 'simpleDiv',
+        [simpleDiv, body, html],
+        [simpleDiv, body, html],
+        [withMargin, simpleDiv, body, html],
+        [divBetweenPseudo, inlineSpan, withMargin, simpleDiv, body, html]);
+}, "elementsFromPoint for each corner of a simple div");
+
+test(function() {
+    checkElementsFromPointFourCorners('document', 'divWithPseudo',
+        [threeD, divWithPseudo, simpleDiv, body, html],
+        [threeD, divWithPseudo, simpleDiv, body, html],
+        [divWithPseudo, simpleDiv, body, html],
+        [divWithPseudo, divBetweenPseudo, divWithPseudo, simpleDiv, body, html]);
+}, "elementsFromPoint for each corner of a div that has a pseudo-element");
+
+test(function() {
+    checkElementsFromPointFourCorners('document', 'divBetweenPseudo',
+        [divWithPseudo, divBetweenPseudo, divWithPseudo, simpleDiv, body, html],
+        [divBetweenPseudo, simpleDiv, body, html],
+        [divBetweenPseudo, inlineSpan, withMargin, simpleDiv, body, html],
+        [divBetweenPseudo, inlineSpan, withMargin, simpleDiv, body, html]);
+}, "elementsFromPoint for each corner of a div that is between another div and its pseudo-element");
+
+test(function() {
+    checkElementsFromPointFourCorners('document', 'withMargin',
+        [withMargin, simpleDiv, body, html],
+        [divBetweenPseudo, inlineSpan, withMargin, simpleDiv, body, html],
+        [withMargin, body, html],
+        [withMargin, body, html]);
+}, "elementsFromPoint for each corner of a div that has a margin");
+
+test(function() {
+    checkElementsFromPointFourCorners('document', 'noPointerEvents',
+        [threeD, divWithPseudo, simpleDiv, body, html],
+        [threeD, divWithPseudo, simpleDiv, body, html],
+        [withMargin, body, html],
+        [withMargin, body, html]);
+}, "elementsFromPoint for each corner of a div with pointer-events:none");
+
+test(function() {
+    checkElementsFromPointFourCorners('document', 'threeD',
+        [threeD, simpleDiv, body, html],
+        [threeD, body, html],
+        [threeD, simpleDiv, body, html],
+        [threeD, body, html]);
+}, "elementsFromPoint for each corner of a div with a 3d transform");
+</script>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-svg-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-svg-expected.txt
new file mode 100644 (file)
index 0000000..1321de6
--- /dev/null
@@ -0,0 +1,6 @@
+
+PASS elementsFromPoint for a point inside two rects 
+PASS elementsFromPoint for a point inside two rects that are inside a <g> 
+PASS elementsFromPoint for a point inside two images 
+PASS elementsFromPoint for a point inside transformed rects and <g> 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-svg.html b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-svg.html
new file mode 100644 (file)
index 0000000..8535228
--- /dev/null
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/elementsFromPoint.js"></script>
+<style>
+html, body {
+    margin: 0;
+    padding: 0;
+}
+#svg {
+    margin: 100px;
+    background-color: rgba(0,180,0,0.2);
+}
+rect {
+    fill: rgba(180,0,0,0.2);
+}
+#topLeftRect2NoHitTest {
+    pointer-events: none;
+}
+</style>
+<div id='sandbox'>
+    <svg id='svg' width='300' height='300'>
+        <rect id='topLeftRect1' x='5' y='5' width='90' height='90'/>
+        <rect id='topLeftRect2NoHitTest' x='10' y='10' width='80' height='80'/>
+        <rect id='topLeftRect3' x='15' y='15' width='70' height='70'/>
+
+        <g id='middleG1'>
+            <g id='middleG2'>
+                <rect id='middleRect1' x='105' y='105' width='90' height='90'/>
+                <rect id='middleRect2' x='110' y='110' width='80' height='80'/>
+            </g>
+        </g>
+
+        <g id='bottomLeftG'>
+            <image id='bottomLeftImage1' x='5' y='205' width='90' height='90' xlink:href='data:image/svg+xml;utf8,<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="rgba(180,0,0,0.2)"/></svg>'/>
+            <image id='bottomLeftImage2' x='10' y='210' width='80' height='80' xlink:href='data:image/svg+xml;utf8,<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="rgba(180,0,0,0.2)"/></svg>'/>
+        </g>
+
+        <g id='bottomRightG1' transform='translate(300, 300)'>
+            <g id='bottomRightG2' transform='translate(-100, -100)'>
+                <rect id='bottomRightRect1' x='5' y='5' width='90' height='90'/>
+                <rect id='bottomRightRect2' x='110' y='110' width='80' height='80' transform='translate(-100, -100)'/>
+            </g>
+        </g>
+    </svg>
+</div>
+<script>
+test(function() {
+    assertElementsFromPoint('document', 125, 125,
+        [topLeftRect3, topLeftRect1, svg, sandbox, document.body, document.documentElement]);
+}, 'elementsFromPoint for a point inside two rects');
+
+test(function() {
+    assertElementsFromPoint('document', 225, 225,
+        [middleRect2, middleRect1, svg, sandbox, document.body, document.documentElement]);
+}, 'elementsFromPoint for a point inside two rects that are inside a <g>');
+
+test(function() {
+    assertElementsFromPoint('document', 125, 325,
+        [bottomLeftImage2, bottomLeftImage1, svg, sandbox, document.body, document.documentElement]);
+}, 'elementsFromPoint for a point inside two images');
+
+test(function() {
+    assertElementsFromPoint('document', 325, 325,
+        [bottomRightRect2, bottomRightRect1, svg, sandbox, document.body, document.documentElement]);
+}, 'elementsFromPoint for a point inside transformed rects and <g>');
+</script>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-table-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-table-expected.txt
new file mode 100644 (file)
index 0000000..dee50a6
--- /dev/null
@@ -0,0 +1,6 @@
+
+PASS elementsFromPoint for points inside table cells 
+PASS elementsFromPoint for points between table cells 
+PASS elementsFromPoint for points inside cells in a right-to-left table 
+PASS elementsFromPoint for points inside cells in a flipped (writing-mode:vertical-lr) table 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-table.html b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-table.html
new file mode 100644 (file)
index 0000000..9ecb614
--- /dev/null
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/elementsFromPoint.js"></script>
+<style>
+html, body {
+    margin: 0;
+    padding: 0;
+}
+#testtable {
+    margin: 100px;
+    width: 200px;
+    height: 200px;
+    background-color: rgba(0,180,0,0.2);
+}
+#testtable tr {
+    background-color: rgba(180,0,0,0.2);
+}
+#testtable td {
+    background-color: rgba(0,0,180,0.2);
+}
+.rtl {
+    direction: rtl;
+}
+.tblr {
+    writing-mode: vertical-lr;
+}
+</style>
+<div id='sandbox'>
+    <table id='testtable'>
+        <tr id='tr1'>
+            <td id='td11'></td>
+            <td id='td12'></td>
+            <td id='td13'></td>
+            <td id='td14'></td>
+        </tr>
+        <tr id='tr2'>
+            <td id='td21'></td>
+            <td id='td22'></td>
+            <td id='td23'></td>
+            <td id='td24'></td>
+        </tr>
+        <tr id='tr3'>
+            <td id='td31'></td>
+            <td id='td32'></td>
+            <td id='td33'></td>
+            <td id='td34'></td>
+        </tr>
+        <tr id='tr4'>
+            <td id='td41'></td>
+            <td id='td42'></td>
+            <td id='td43'></td>
+            <td id='td44'></td>
+        </tr>
+    </table>
+</div>
+<script>
+test(function() {
+    assertElementsFromPoint('document', 125, 125,
+        [td11, testtable, sandbox, document.body, document.documentElement]);
+    assertElementsFromPoint('document', 275, 125,
+        [td14, testtable, sandbox, document.body, document.documentElement]);
+    assertElementsFromPoint('document', 175, 175,
+        [td22, testtable, sandbox, document.body, document.documentElement]);
+    assertElementsFromPoint('document', 125, 275,
+        [td41, testtable, sandbox, document.body, document.documentElement]);
+}, 'elementsFromPoint for points inside table cells');
+
+test(function() {
+    assertElementsFromPoint('document', 100, 100,
+        [testtable, sandbox, document.body, document.documentElement]);
+    assertElementsFromPoint('document', 199, 199,
+        [testtable, sandbox, document.body, document.documentElement]);
+}, 'elementsFromPoint for points between table cells');
+
+testtable.setAttribute('class', 'rtl');
+test(function() {
+    assertElementsFromPoint('document', 125, 125,
+        [td14, testtable, sandbox, document.body, document.documentElement]);
+    assertElementsFromPoint('document', 275, 125,
+        [td11, testtable, sandbox, document.body, document.documentElement]);
+    assertElementsFromPoint('document', 100, 100,
+        [testtable, sandbox, document.body, document.documentElement]);
+    assertElementsFromPoint('document', 199, 199,
+        [testtable, sandbox, document.body, document.documentElement]);
+}, 'elementsFromPoint for points inside cells in a right-to-left table');
+
+testtable.setAttribute('class', 'tblr');
+test(function() {
+    assertElementsFromPoint('document', 125, 275,
+        [td14, testtable, sandbox, document.body, document.documentElement]);
+    assertElementsFromPoint('document', 275, 125,
+        [td41, testtable, sandbox, document.body, document.documentElement]);
+    assertElementsFromPoint('document', 100, 100,
+        [testtable, sandbox, document.body, document.documentElement]);
+    assertElementsFromPoint('document', 199, 199,
+        [testtable, sandbox, document.body, document.documentElement]);
+}, 'elementsFromPoint for points inside cells in a flipped (writing-mode:vertical-lr) table');
+</script>
index 899b9cf673a0c556f97530062fbec6c9b9a628c4..ef9c08cf125cd2d9aafe0df4e5159625a6718752 100644 (file)
@@ -1,6 +1,5 @@
 Hello
 
 PASS cssom-view - elementFromPoint and elementsFromPoint dealing with negative margins 
-FAIL cssom-view - elementFromPoint and elementsFromPoint dealing with negative margins 1 document.elementsFromPoint is not a function. (In 'document.elementsFromPoint(outerRect.left + 1,
-                                                    outerRect.top + 1)', 'document.elementsFromPoint' is undefined)
+FAIL cssom-view - elementFromPoint and elementsFromPoint dealing with negative margins 1 assert_array_equals: elementsFromPoint should get sequence [inner, outer, body, html] lengths differ, expected 4 got 5
 
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/resources/elementsFromPoint.js b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/resources/elementsFromPoint.js
new file mode 100644 (file)
index 0000000..ba986ef
--- /dev/null
@@ -0,0 +1,48 @@
+function nodeToString(node) {
+  var str = '';
+  if (node.nodeType == Node.ELEMENT_NODE) {
+    str += node.nodeName;
+    if (node.id)
+      str += '#' + node.id;
+    else if (node.class)
+      str += '.' + node.class;
+  } else if (node.nodeType == Node.TEXT_NODE) {
+    str += '\'' + node.data + '\'';
+  } else if (node.nodeType == Node.DOCUMENT_NODE) {
+    str += '#document';
+  }
+  return str;
+}
+
+function nodeListToString(nodes) {
+  var nodeString = '';
+
+  for (var i = 0; i < nodes.length; i++) {
+    var str = nodeToString(nodes[i]);
+    if (!str)
+      continue;
+    nodeString += str;
+    if (i + 1 < nodes.length)
+      nodeString += ', ';
+  }
+  return nodeString;
+}
+
+function assertElementsFromPoint(doc, x, y, expected) {
+  var query = doc + '.elementsFromPoint(' + x + ',' + y + ')';
+  var sequence = eval(query);
+  assert_equals(nodeListToString(sequence), nodeListToString(expected), query);
+}
+
+function checkElementsFromPointFourCorners(doc, element, expectedTopLeft, expectedTopRight, expectedBottomLeft, expectedBottomRight) {
+  var rect = eval(doc + '.getElementById(\'' + element + '\')').getBoundingClientRect();
+  var topLeft = {x: rect.left + 1, y: rect.top + 1};
+  var topRight = {x: rect.right - 1, y: rect.top + 1};
+  var bottomLeft = {x: rect.left + 1, y: rect.bottom - 1};
+  var bottomRight = {x: rect.right - 1, y: rect.bottom - 1};
+
+  assertElementsFromPoint(doc, topLeft.x, topLeft.y, expectedTopLeft);
+  assertElementsFromPoint(doc, topRight.x, topRight.y, expectedTopRight);
+  assertElementsFromPoint(doc, bottomLeft.x, bottomLeft.y, expectedBottomLeft);
+  assertElementsFromPoint(doc, bottomRight.x, bottomRight.y, expectedBottomRight);
+}
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/resources/iframe1.html b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/resources/iframe1.html
new file mode 100644 (file)
index 0000000..ec46994
--- /dev/null
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<style>
+html, body {
+    margin: 0;
+    padding: 0;
+}
+#div {
+    width: 100px;
+    height: 100px;
+    background: red;
+}
+</style>
+<div id='div'></div>
+<script>
+window.onload = window.parent.onFrameLoaded();
+</script>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/cssom-view/resources/iframe2.html b/LayoutTests/imported/w3c/web-platform-tests/cssom-view/resources/iframe2.html
new file mode 100644 (file)
index 0000000..7bb944c
--- /dev/null
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<style>
+html, body {
+    margin: 0;
+    padding: 0;
+}
+#big {
+    width: 125px;
+    height: 500px;
+    background: blue;
+}
+#small {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100px;
+    height: 100px;
+    background: green;
+}
+</style>
+<div id='big'></div>
+<div id='small'></div>
+<script>
+window.onload = window.parent.onFrameLoaded();
+</script>
index 57585fbc808d70c798a3e9c8cf7070ad729ec184..6384509fcfa75f86d54c2f88b7927a37f207a1a1 100644 (file)
@@ -1,3 +1,88 @@
+2017-07-26  Ali Juma  <ajuma@chromium.org>
+
+        Implement document.elementsFromPoint
+        https://bugs.webkit.org/show_bug.cgi?id=153137
+
+        Reviewed by Simon Fraser.
+
+        This ports Blink's implementation of elementsFromPoint, from the
+        following patches by Philip Rogers (pdr@chromium.org):
+        -https://src.chromium.org/viewvc/blink?revision=190686&view=revision
+        -https://src.chromium.org/viewvc/blink?revision=191240&view=revision
+        -https://src.chromium.org/viewvc/blink?revision=199214&view=revision
+
+        Tests: imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-iframes.html
+               imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-invalid-cases.html
+               imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-shadowroot.html
+               imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-simple.html
+               imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-svg.html
+               imported/w3c/web-platform-tests/cssom-view/elementsFromPoint-table.html
+
+        * dom/DocumentOrShadowRoot.idl:
+        * dom/TreeScope.cpp:
+        (WebCore::absolutePointIfNotClipped):
+        (WebCore::TreeScope::nodeFromPoint):
+        (WebCore::TreeScope::elementFromPoint):
+        (WebCore::TreeScope::elementsFromPoint):
+        * dom/TreeScope.h:
+        * page/EventHandler.cpp:
+        (WebCore::EventHandler::hitTestResultAtPoint):
+        * rendering/EllipsisBox.cpp:
+        (WebCore::EllipsisBox::nodeAtPoint):
+        * rendering/HitTestRequest.h:
+        (WebCore::HitTestRequest::HitTestRequest):
+        (WebCore::HitTestRequest::resultIsElementList):
+        (WebCore::HitTestRequest::includesAllElementsUnderPoint):
+        * rendering/HitTestResult.cpp:
+        (WebCore::HitTestResult::HitTestResult):
+        (WebCore::HitTestResult::operator=):
+        (WebCore::HitTestResult::addNodeToListBasedTestResult):
+        (WebCore::HitTestResult::append):
+        (WebCore::HitTestResult::listBasedTestResult):
+        (WebCore::HitTestResult::mutableListBasedTestResult):
+        (WebCore::HitTestResult::addNodeToRectBasedTestResult): Deleted.
+        (WebCore::HitTestResult::rectBasedTestResult): Deleted.
+        (WebCore::HitTestResult::mutableRectBasedTestResult): Deleted.
+        * rendering/HitTestResult.h:
+        * rendering/InlineFlowBox.cpp:
+        (WebCore::InlineFlowBox::nodeAtPoint):
+        * rendering/InlineTextBox.cpp:
+        (WebCore::InlineTextBox::nodeAtPoint):
+        * rendering/RenderBlock.cpp:
+        (WebCore::RenderBlock::nodeAtPoint):
+        * rendering/RenderBox.cpp:
+        (WebCore::RenderBox::nodeAtPoint):
+        * rendering/RenderImage.cpp:
+        (WebCore::RenderImage::nodeAtPoint):
+        * rendering/RenderInline.cpp:
+        (WebCore::RenderInline::hitTestCulledInline):
+        * rendering/RenderLayer.cpp:
+        (WebCore::RenderLayer::hitTestFixedLayersInNamedFlows):
+        (WebCore::RenderLayer::hitTestLayer):
+        (WebCore::RenderLayer::hitTestContents):
+        (WebCore::RenderLayer::hitTestList):
+        (WebCore::RenderLayer::calculateClipRects):
+        * rendering/RenderTable.cpp:
+        (WebCore::RenderTable::nodeAtPoint):
+        * rendering/RenderTableSection.cpp:
+        (WebCore::RenderTableSection::nodeAtPoint):
+        * rendering/RenderWidget.cpp:
+        (WebCore::RenderWidget::nodeAtPoint):
+        * rendering/SimpleLineLayoutFunctions.cpp:
+        (WebCore::SimpleLineLayout::hitTestFlow):
+        * rendering/svg/RenderSVGContainer.cpp:
+        (WebCore::RenderSVGContainer::nodeAtFloatPoint):
+        * rendering/svg/RenderSVGImage.cpp:
+        (WebCore::RenderSVGImage::nodeAtFloatPoint):
+        * rendering/svg/RenderSVGRoot.cpp:
+        (WebCore::RenderSVGRoot::nodeAtPoint):
+        * rendering/svg/RenderSVGShape.cpp:
+        (WebCore::RenderSVGShape::nodeAtFloatPoint):
+        * rendering/svg/SVGInlineTextBox.cpp:
+        (WebCore::SVGInlineTextBox::nodeAtPoint):
+        * testing/Internals.cpp:
+        (WebCore::Internals::nodesFromRect):
+
 2017-07-26  Charlie Turner  <cturner@igalia.com>
 
         [GStreamer] Review WebKitWebSource after r219252.
index 99661df5bfc5046ca8d583bbf9f9477af6a90217..9a6bcd0bccba4766d5865b02702171270b30b6d1 100644 (file)
@@ -31,7 +31,7 @@
     // Extensions from Shadow DOM API (https://w3c.github.io/webcomponents/spec/shadow/#extensions-to-the-documentorshadowroot-mixin).
     // DOMSelection? getSelection(); // FIXME: We currently have this on Document only.
     Element? elementFromPoint(double x, double y);
-    // sequence<Element> elementsFromPoint(double x, double y); // FIXME: Implement this.
+    sequence<Element> elementsFromPoint(double x, double y);
     // CaretPosition? caretPositionFromPoint(double x, double y); // FIXME: Implement this.
     readonly attribute Element? activeElement;
     // readonly attribute StyleSheetList styleSheets; // FIXME: Implement this.
index 280dd209c77351a9f94c56f4275724519de829e8..473a1b30073c6d7f1d750f2056b3e89a5e056628 100644 (file)
@@ -42,6 +42,7 @@
 #include "NodeRareData.h"
 #include "Page.h"
 #include "PointerLockController.h"
+#include "PseudoElement.h"
 #include "RenderView.h"
 #include "RuntimeEnabledFeatures.h"
 #include "Settings.h"
@@ -295,39 +296,46 @@ HTMLLabelElement* TreeScope::labelElementForId(const AtomicString& forAttributeV
     return m_labelsByForAttribute->getElementByLabelForAttribute(*forAttributeValue.impl(), *this);
 }
 
-Node* TreeScope::nodeFromPoint(const LayoutPoint& clientPoint, LayoutPoint* localPoint)
+static std::optional<LayoutPoint> absolutePointIfNotClipped(Document& document, const LayoutPoint& clientPoint)
 {
-    auto* frame = documentScope().frame();
-    auto* view = documentScope().view();
+    auto* frame = document.frame();
+    auto* view = document.view();
     if (!frame || !view)
-        return nullptr;
+        return std::nullopt;
 
-    LayoutPoint absolutePoint;
     if (frame->settings().visualViewportEnabled()) {
-        documentScope().updateLayout();
+        document.updateLayout();
         FloatPoint layoutViewportPoint = view->clientToLayoutViewportPoint(clientPoint);
         FloatRect layoutViewportBounds({ }, view->layoutViewportRect().size());
         if (!layoutViewportBounds.contains(layoutViewportPoint))
-            return nullptr;
-        absolutePoint = LayoutPoint(view->layoutViewportToAbsolutePoint(layoutViewportPoint));
-    } else {
-        float scaleFactor = frame->pageZoomFactor() * frame->frameScaleFactor();
+            return std::nullopt;
+        return LayoutPoint(view->layoutViewportToAbsolutePoint(layoutViewportPoint));
+    }
 
-        absolutePoint = clientPoint;
-        absolutePoint.scale(scaleFactor);
-        absolutePoint.moveBy(view->contentsScrollPosition());
+    float scaleFactor = frame->pageZoomFactor() * frame->frameScaleFactor();
 
-        LayoutRect visibleRect;
+    LayoutPoint absolutePoint = clientPoint;
+    absolutePoint.scale(scaleFactor);
+    absolutePoint.moveBy(view->contentsScrollPosition());
+
+    LayoutRect visibleRect;
 #if PLATFORM(IOS)
-        visibleRect = view->unobscuredContentRect();
+    visibleRect = view->unobscuredContentRect();
 #else
-        visibleRect = view->visibleContentRect();
+    visibleRect = view->visibleContentRect();
 #endif
-        if (!visibleRect.contains(absolutePoint))
-            return nullptr;
-    }
+    if (visibleRect.contains(absolutePoint))
+        return absolutePoint;
+    return std::nullopt;
+}
 
-    HitTestResult result(absolutePoint);
+Node* TreeScope::nodeFromPoint(const LayoutPoint& clientPoint, LayoutPoint* localPoint)
+{
+    auto absolutePoint = absolutePointIfNotClipped(documentScope(), clientPoint);
+    if (!absolutePoint)
+        return nullptr;
+
+    HitTestResult result(absolutePoint.value());
     documentScope().renderView()->hitTest(HitTestRequest(), result);
 
     if (localPoint)
@@ -336,13 +344,13 @@ Node* TreeScope::nodeFromPoint(const LayoutPoint& clientPoint, LayoutPoint* loca
     return result.innerNode();
 }
 
-Element* TreeScope::elementFromPoint(double x, double y)
+RefPtr<Element> TreeScope::elementFromPoint(double clientX, double clientY)
 {
     Document& document = documentScope();
     if (!document.hasLivingRenderTree())
         return nullptr;
 
-    Node* node = nodeFromPoint(LayoutPoint(x, y), nullptr);
+    Node* node = nodeFromPoint(LayoutPoint(clientX, clientY), nullptr);
     if (!node)
         return nullptr;
 
@@ -357,6 +365,62 @@ Element* TreeScope::elementFromPoint(double x, double y)
     return downcast<Element>(node);
 }
 
+Vector<RefPtr<Element>> TreeScope::elementsFromPoint(double clientX, double clientY)
+{
+    Vector<RefPtr<Element>> elements;
+
+    Document& document = documentScope();
+    if (!document.hasLivingRenderTree())
+        return elements;
+
+    auto absolutePoint = absolutePointIfNotClipped(document, LayoutPoint(clientX, clientY));
+    if (!absolutePoint)
+        return elements;
+
+    HitTestRequest request(HitTestRequest::ReadOnly
+        | HitTestRequest::Active
+        | HitTestRequest::DisallowUserAgentShadowContent
+        | HitTestRequest::CollectMultipleElements
+        | HitTestRequest::IncludeAllElementsUnderPoint);
+    HitTestResult result(absolutePoint.value());
+    documentScope().renderView()->hitTest(request, result);
+
+    Node* lastNode = nullptr;
+    for (auto listBasedNode : result.listBasedTestResult()) {
+        Node* node = listBasedNode.get();
+        node = &retargetToScope(*node);
+        while (!is<Element>(*node)) {
+            node = node->parentInComposedTree();
+            if (!node)
+                break;
+            node = &retargetToScope(*node);
+        }
+
+        if (!node)
+            continue;
+
+        if (is<PseudoElement>(node))
+            node = downcast<PseudoElement>(*node).hostElement();
+
+        // Prune duplicate entries. A pseudo ::before content above its parent
+        // node should only result in one entry.
+        if (node == lastNode)
+            continue;
+
+        elements.append(downcast<Element>(node));
+        lastNode = node;
+    }
+
+    if (m_rootNode.isDocumentNode()) {
+        if (Element* rootElement = downcast<Document>(m_rootNode).documentElement()) {
+            if (elements.isEmpty() || elements.last() != rootElement)
+                elements.append(rootElement);
+        }
+    }
+
+    return elements;
+}
+
 Element* TreeScope::findAnchor(const String& name)
 {
     if (name.isEmpty())
index ddefbe3d913419c21331679851161ac8d3b2b97f..f0ff4c34e004a466c4d42180ee90f14e538ecd5e 100644 (file)
@@ -29,6 +29,7 @@
 #include "DocumentOrderedMap.h"
 #include <memory>
 #include <wtf/Forward.h>
+#include <wtf/Vector.h>
 #include <wtf/text/AtomicString.h>
 
 namespace WebCore {
@@ -87,7 +88,8 @@ public:
     void removeLabel(const AtomicStringImpl& forAttributeValue, HTMLLabelElement&);
     HTMLLabelElement* labelElementForId(const AtomicString& forAttributeValue);
 
-    WEBCORE_EXPORT Element* elementFromPoint(double x, double y);
+    WEBCORE_EXPORT RefPtr<Element> elementFromPoint(double clientX, double clientY);
+    WEBCORE_EXPORT Vector<RefPtr<Element>> elementsFromPoint(double clientX, double clientY);
 
     // Find first anchor with the given name.
     // First searches for an element with the given ID, but if that fails, then looks
index 7ef0bfa3c94be7ff05af6a475ac573e1770ab6f7..816ebb2005afc83447fec1125c439798ab64e220 100644 (file)
@@ -1122,6 +1122,8 @@ DragSourceAction EventHandler::updateDragSourceActionsAllowed() const
 
 HitTestResult EventHandler::hitTestResultAtPoint(const LayoutPoint& point, HitTestRequest::HitTestRequestType hitType, const LayoutSize& padding) const
 {
+    ASSERT((hitType & HitTestRequest::CollectMultipleElements) || padding.isEmpty());
+
     Ref<Frame> protectedFrame(m_frame);
 
     // We always send hitTestResultAtPoint to the main frame if we have one,
index fec6f207e959c0f5c12355cbfb9b7b4c0495890b..34701f80c5fb1f04ed2768b0f86c169f2cdc9ae6 100644 (file)
@@ -161,7 +161,7 @@ bool EllipsisBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& resu
     LayoutRect boundsRect(adjustedLocation, LayoutSize(m_logicalWidth, m_height));
     if (visibleToHitTesting() && boundsRect.intersects(HitTestLocation::rectForPoint(locationInContainer.point(), 0, 0, 0, 0))) {
         blockFlow().updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation));
-        if (!result.addNodeToRectBasedTestResult(blockFlow().element(), request, locationInContainer, boundsRect))
+        if (result.addNodeToListBasedTestResult(blockFlow().element(), request, locationInContainer, boundsRect) == HitTestProgress::Stop)
             return true;
     }
 
index ca2b9a6f0775dd336303371d9e77d65ef3ea08df..1cf51447b40ee491f3dfa3ad6523987d89867ccf 100644 (file)
@@ -22,6 +22,8 @@
 
 #pragma once
 
+#include <wtf/Assertions.h>
+
 namespace WebCore {
 
 class HitTestRequest {
@@ -38,7 +40,11 @@ public:
         AllowFrameScrollbars = 1 << 9,
         AllowChildFrameContent = 1 << 10,
         ChildFrameHitTest = 1 << 11,
-        AccessibilityHitTest = 1 << 12
+        AccessibilityHitTest = 1 << 12,
+        // Collect a list of nodes instead of just one. Used for elementsFromPoint and rect-based tests.
+        CollectMultipleElements = 1 << 13,
+        // When using list-based testing, continue hit testing even after a hit has been found.
+        IncludeAllElementsUnderPoint = 1 << 14
     };
 
     typedef unsigned HitTestRequestType;
@@ -46,6 +52,7 @@ public:
     HitTestRequest(HitTestRequestType requestType = ReadOnly | Active | DisallowUserAgentShadowContent)
         : m_requestType(requestType)
     {
+        ASSERT(!(requestType & IncludeAllElementsUnderPoint) || (requestType & CollectMultipleElements));
     }
 
     bool readOnly() const { return m_requestType & ReadOnly; }
@@ -60,6 +67,8 @@ public:
     bool allowsFrameScrollbars() const { return m_requestType & AllowFrameScrollbars; }
     bool allowsChildFrameContent() const { return m_requestType & AllowChildFrameContent; }
     bool isChildFrameHitTest() const { return m_requestType & ChildFrameHitTest; }
+    bool resultIsElementList() const { return m_requestType & CollectMultipleElements; }
+    bool includesAllElementsUnderPoint() const { return m_requestType & IncludeAllElementsUnderPoint; }
 
     // Convenience functions
     bool touchMove() const { return move() && touchEvent(); }
index edb6c1b8e00e937bec07a37bcd51974eab88b04a..b7270a502aeed7471ced636d61ed5e5ff87f7ad9 100644 (file)
@@ -95,8 +95,8 @@ HitTestResult::HitTestResult(const HitTestResult& other)
     , m_scrollbar(other.scrollbar())
     , m_isOverWidget(other.isOverWidget())
 {
-    // Only copy the NodeSet in case of rect hit test.
-    m_rectBasedTestResult = other.m_rectBasedTestResult ? std::make_unique<NodeSet>(*other.m_rectBasedTestResult) : nullptr;
+    // Only copy the NodeSet in case of list hit test.
+    m_listBasedTestResult = other.m_listBasedTestResult ? std::make_unique<NodeSet>(*other.m_listBasedTestResult) : nullptr;
 }
 
 HitTestResult::~HitTestResult()
@@ -114,8 +114,8 @@ HitTestResult& HitTestResult::operator=(const HitTestResult& other)
     m_scrollbar = other.scrollbar();
     m_isOverWidget = other.isOverWidget();
 
-    // Only copy the NodeSet in case of rect hit test.
-    m_rectBasedTestResult = other.m_rectBasedTestResult ? std::make_unique<NodeSet>(*other.m_rectBasedTestResult) : nullptr;
+    // Only copy the NodeSet in case of list hit test.
+    m_listBasedTestResult = other.m_listBasedTestResult ? std::make_unique<NodeSet>(*other.m_listBasedTestResult) : nullptr;
 
     return *this;
 }
@@ -656,49 +656,55 @@ bool HitTestResult::isContentEditable() const
     return m_innerNonSharedNode->hasEditableStyle();
 }
 
-bool HitTestResult::addNodeToRectBasedTestResult(Node* node, const HitTestRequest& request, const HitTestLocation& locationInContainer, const LayoutRect& rect)
+HitTestProgress HitTestResult::addNodeToListBasedTestResult(Node* node, const HitTestRequest& request, const HitTestLocation& locationInContainer, const LayoutRect& rect)
 {
-    // If it is not a rect-based hit test, this method has to be no-op.
-    // Return false, so the hit test stops.
-    if (!isRectBasedTest())
-        return false;
+    // If it is not a list-based hit test, this method has to be no-op.
+    if (!request.resultIsElementList()) {
+        ASSERT(!isRectBasedTest());
+        return HitTestProgress::Stop;
+    }
 
-    // If node is null, return true so the hit test can continue.
     if (!node)
-        return true;
+        return HitTestProgress::Continue;
 
     if (request.disallowsUserAgentShadowContent() && node->isInUserAgentShadowTree())
         node = node->document().ancestorNodeInThisScope(node);
 
-    mutableRectBasedTestResult().add(node);
+    mutableListBasedTestResult().add(node);
+
+    if (request.includesAllElementsUnderPoint())
+        return HitTestProgress::Continue;
 
     bool regionFilled = rect.contains(locationInContainer.boundingBox());
-    return !regionFilled;
+    return regionFilled ? HitTestProgress::Stop : HitTestProgress::Continue;
 }
 
-bool HitTestResult::addNodeToRectBasedTestResult(Node* node, const HitTestRequest& request, const HitTestLocation& locationInContainer, const FloatRect& rect)
+HitTestProgress HitTestResult::addNodeToListBasedTestResult(Node* node, const HitTestRequest& request, const HitTestLocation& locationInContainer, const FloatRect& rect)
 {
-    // If it is not a rect-based hit test, this method has to be no-op.
-    // Return false, so the hit test stops.
-    if (!isRectBasedTest())
-        return false;
+    // If it is not a list-based hit test, this method has to be no-op.
+    if (!request.resultIsElementList()) {
+        ASSERT(!isRectBasedTest());
+        return HitTestProgress::Stop;
+    }
 
-    // If node is null, return true so the hit test can continue.
     if (!node)
-        return true;
+        return HitTestProgress::Continue;
 
     if (request.disallowsUserAgentShadowContent() && node->isInUserAgentShadowTree())
         node = node->document().ancestorNodeInThisScope(node);
 
-    mutableRectBasedTestResult().add(node);
+    mutableListBasedTestResult().add(node);
+
+    if (request.includesAllElementsUnderPoint())
+        return HitTestProgress::Continue;
 
     bool regionFilled = rect.contains(locationInContainer.boundingBox());
-    return !regionFilled;
+    return regionFilled ? HitTestProgress::Stop : HitTestProgress::Continue;
 }
 
-void HitTestResult::append(const HitTestResult& other)
+void HitTestResult::append(const HitTestResult& other, const HitTestRequest& request)
 {
-    ASSERT(isRectBasedTest() && other.isRectBasedTest());
+    ASSERT_UNUSED(request, request.resultIsElementList());
 
     if (!m_innerNode && other.innerNode()) {
         m_innerNode = other.innerNode();
@@ -710,25 +716,25 @@ void HitTestResult::append(const HitTestResult& other)
         m_isOverWidget = other.isOverWidget();
     }
 
-    if (other.m_rectBasedTestResult) {
-        NodeSet& set = mutableRectBasedTestResult();
-        for (NodeSet::const_iterator it = other.m_rectBasedTestResult->begin(), last = other.m_rectBasedTestResult->end(); it != last; ++it)
-            set.add(it->get());
+    if (other.m_listBasedTestResult) {
+        NodeSet& set = mutableListBasedTestResult();
+        for (auto node : *other.m_listBasedTestResult)
+            set.add(node.get());
     }
 }
 
-const HitTestResult::NodeSet& HitTestResult::rectBasedTestResult() const
+const HitTestResult::NodeSet& HitTestResult::listBasedTestResult() const
 {
-    if (!m_rectBasedTestResult)
-        m_rectBasedTestResult = std::make_unique<NodeSet>();
-    return *m_rectBasedTestResult;
+    if (!m_listBasedTestResult)
+        m_listBasedTestResult = std::make_unique<NodeSet>();
+    return *m_listBasedTestResult;
 }
 
-HitTestResult::NodeSet& HitTestResult::mutableRectBasedTestResult()
+HitTestResult::NodeSet& HitTestResult::mutableListBasedTestResult()
 {
-    if (!m_rectBasedTestResult)
-        m_rectBasedTestResult = std::make_unique<NodeSet>();
-    return *m_rectBasedTestResult;
+    if (!m_listBasedTestResult)
+        m_listBasedTestResult = std::make_unique<NodeSet>();
+    return *m_listBasedTestResult;
 }
 
 Vector<String> HitTestResult::dictationAlternatives() const
index 143220552136923b4e50c6f62a7870ebef4c4cf7..ba2d7a2a590b5a7454b5af4dcb96862ac9f802ed 100644 (file)
@@ -40,6 +40,8 @@ class Node;
 class Scrollbar;
 class URL;
 
+enum class HitTestProgress { Stop, Continue };
+
 class HitTestResult {
 public:
     typedef ListHashSet<RefPtr<Node>> NodeSet;
@@ -131,16 +133,14 @@ public:
     WEBCORE_EXPORT bool isOverTextInsideFormControlElement() const;
     WEBCORE_EXPORT bool allowsCopy() const;
 
-    // Returns true if it is rect-based hit test and needs to continue until the rect is fully
-    // enclosed by the boundaries of a node.
-    bool addNodeToRectBasedTestResult(Node*, const HitTestRequest&, const HitTestLocation& pointInContainer, const LayoutRect& = LayoutRect());
-    bool addNodeToRectBasedTestResult(Node*, const HitTestRequest&, const HitTestLocation& pointInContainer, const FloatRect&);
-    void append(const HitTestResult&);
+    HitTestProgress addNodeToListBasedTestResult(Node*, const HitTestRequest&, const HitTestLocation& pointInContainer, const LayoutRect& = LayoutRect());
+    HitTestProgress addNodeToListBasedTestResult(Node*, const HitTestRequest&, const HitTestLocation& pointInContainer, const FloatRect&);
+    void append(const HitTestResult&, const HitTestRequest&);
 
-    // If m_rectBasedTestResult is 0 then set it to a new NodeSet. Return *m_rectBasedTestResult. Lazy allocation makes
+    // If m_listBasedTestResult is 0 then set it to a new NodeSet. Return *m_listBasedTestResult. Lazy allocation makes
     // sense because the NodeSet is seldom necessary, and it's somewhat expensive to allocate and initialize. This method does
-    // the same thing as mutableRectBasedTestResult(), but here the return value is const.
-    WEBCORE_EXPORT const NodeSet& rectBasedTestResult() const;
+    // the same thing as mutableListBasedTestResult(), but here the return value is const.
+    WEBCORE_EXPORT const NodeSet& listBasedTestResult() const;
 
     Vector<String> dictationAlternatives() const;
 
@@ -148,7 +148,7 @@ public:
     WEBCORE_EXPORT Element* targetElement() const;
 
 private:
-    NodeSet& mutableRectBasedTestResult(); // See above.
+    NodeSet& mutableListBasedTestResult(); // See above.
 
 #if ENABLE(VIDEO)
     HTMLMediaElement* mediaElement() const;
@@ -164,7 +164,7 @@ private:
     RefPtr<Scrollbar> m_scrollbar;
     bool m_isOverWidget; // Returns true if we are over a widget (and not in the border/padding area of a RenderWidget for example).
 
-    mutable std::unique_ptr<NodeSet> m_rectBasedTestResult;
+    mutable std::unique_ptr<NodeSet> m_listBasedTestResult;
 };
 
 WEBCORE_EXPORT String displayString(const String&, const Node*);
index 8df789e0b742123511614ce5f6f83e228b76c4fc..a70474fc40f08d804369a9a451ab1e0d6173dd40 100644 (file)
@@ -1150,7 +1150,7 @@ bool InlineFlowBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& re
 
     if (locationInContainer.intersects(rect)) {
         renderer().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset))); // Don't add in m_x or m_y here, we want coords in the containing block's space.
-        if (!result.addNodeToRectBasedTestResult(renderer().element(), request, locationInContainer, rect))
+        if (result.addNodeToListBasedTestResult(renderer().element(), request, locationInContainer, rect) == HitTestProgress::Stop)
             return true;
     }
 
index eb5d6498b3f0aa425499adc66ccb4a87a168723f..9d45e1876d7938742a285e903277a543808890b7 100644 (file)
@@ -359,7 +359,7 @@ bool InlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& re
 
     if (locationInContainer.intersects(rect)) {
         renderer().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset)));
-        if (!result.addNodeToRectBasedTestResult(renderer().textNode(), request, locationInContainer, rect))
+        if (result.addNodeToListBasedTestResult(renderer().textNode(), request, locationInContainer, rect) == HitTestProgress::Stop)
             return true;
     }
     return false;
index 2b4149a2b5ef7205fdbf8c15543f14358354bde9..907811255bffb20d8eaeeaa762d4d9cc6d5c247e 100644 (file)
@@ -2494,7 +2494,7 @@ bool RenderBlock::nodeAtPoint(const HitTestRequest& request, HitTestResult& resu
     if ((hitTestAction == HitTestBlockBackground || hitTestAction == HitTestChildBlockBackground) && isPointInOverflowControl(result, locationInContainer.point(), adjustedLocation)) {
         updateHitTestResult(result, locationInContainer.point() - localOffset);
         // FIXME: isPointInOverflowControl() doesn't handle rect-based tests yet.
-        if (!result.addNodeToRectBasedTestResult(nodeForHitTest(), request, locationInContainer))
+        if (result.addNodeToListBasedTestResult(nodeForHitTest(), request, locationInContainer) == HitTestProgress::Stop)
            return true;
     }
 
@@ -2574,7 +2574,7 @@ bool RenderBlock::nodeAtPoint(const HitTestRequest& request, HitTestResult& resu
         LayoutRect boundsRect(adjustedLocation, size());
         if (visibleToHitTesting() && locationInContainer.intersects(boundsRect)) {
             updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - localOffset));
-            if (!result.addNodeToRectBasedTestResult(nodeForHitTest(), request, locationInContainer, boundsRect))
+            if (result.addNodeToListBasedTestResult(nodeForHitTest(), request, locationInContainer, boundsRect) == HitTestProgress::Stop)
                 return true;
         }
     }
index 27ed07212a1f8c024f5a22813537c9d63589d799..d8b8429fa71eea1bc256ed396fd34a60de9b8119 100644 (file)
@@ -1244,7 +1244,7 @@ bool RenderBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result
     boundsRect.moveBy(adjustedLocation);
     if (visibleToHitTesting() && action == HitTestForeground && locationInContainer.intersects(boundsRect)) {
         updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation));
-        if (!result.addNodeToRectBasedTestResult(element(), request, locationInContainer, boundsRect))
+        if (result.addNodeToListBasedTestResult(element(), request, locationInContainer, boundsRect) == HitTestProgress::Stop)
             return true;
     }
 
index 1e618502f86613387f10970b1fa03ad146b6311d..5b236d3f7e8674850af3fb493564d61a3dae93bd 100644 (file)
@@ -667,8 +667,8 @@ bool RenderImage::nodeAtPoint(const HitTestRequest& request, HitTestResult& resu
         }
     }
 
-    if (!inside && result.isRectBasedTest())
-        result.append(tempResult);
+    if (!inside && request.resultIsElementList())
+        result.append(tempResult, request);
     if (inside)
         result = tempResult;
     return inside;
index 8b529030bf66ba67fda73d5f0f5f5ed957fde6f4..ad8d345518a771ca7640223d7d2b2319c08f38c1 100644 (file)
@@ -959,9 +959,9 @@ bool RenderInline::hitTestCulledInline(const HitTestRequest& request, HitTestRes
 
     if (context.intersected()) {
         updateHitTestResult(result, tmpLocation.point());
-        // We can not use addNodeToRectBasedTestResult to determine if we fully enclose the hit-test area
+        // We cannot use addNodeToListBasedTestResult to determine if we fully enclose the hit-test area
         // because it can only handle rectangular targets.
-        result.addNodeToRectBasedTestResult(element(), request, locationInContainer);
+        result.addNodeToListBasedTestResult(element(), request, locationInContainer);
         return regionResult.contains(tmpLocation.boundingBox());
     }
     return false;
index d8f37931133692059882e534625f925c514d07d4..92ddde6b0f7e196048e5907cfc15b20c86738d14 100644 (file)
@@ -5097,14 +5097,15 @@ RenderLayer* RenderLayer::hitTestFixedLayersInNamedFlows(RenderLayer* /*rootLaye
         RenderLayer* hitLayer = fixedLayer->hitTestLayer(fixedLayer->renderer().flowThreadContainingBlock()->layer(), nullptr, request, tempResult,
             hitTestRect, hitTestLocation, false, transformState, zOffsetForDescendants);
 
-        // If it a rect-based test, we can safely append the temporary result since it might had hit
+        // If it a list-based test, we can safely append the temporary result since it might had hit
         // nodes but not necesserily had hitLayer set.
-        if (result.isRectBasedTest())
-            result.append(tempResult);
+        ASSERT(!result.isRectBasedTest() || request.resultIsElementList());
+        if (request.resultIsElementList())
+            result.append(tempResult, request);
 
         if (isHitCandidate(hitLayer, depthSortDescendants, zOffset, unflattenedTransformState)) {
             resultLayer = hitLayer;
-            if (!result.isRectBasedTest())
+            if (!request.resultIsElementList())
                 result = tempResult;
             if (!depthSortDescendants)
                 break;
@@ -5271,16 +5272,16 @@ RenderLayer* RenderLayer::hitTestLayer(RenderLayer* rootLayer, RenderLayer* cont
         bool insideFragmentForegroundRect = false;
         if (hitTestContentsForFragments(layerFragments, request, tempResult, hitTestLocation, HitTestDescendants, insideFragmentForegroundRect)
             && isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) {
-            if (result.isRectBasedTest())
-                result.append(tempResult);
+            if (request.resultIsElementList())
+                result.append(tempResult, request);
             else
                 result = tempResult;
             if (!depthSortDescendants)
                 return this;
             // Foreground can depth-sort with descendant layers, so keep this as a candidate.
             candidateLayer = this;
-        } else if (insideFragmentForegroundRect && result.isRectBasedTest())
-            result.append(tempResult);
+        } else if (insideFragmentForegroundRect && request.resultIsElementList())
+            result.append(tempResult, request);
     }
 
     // Now check our negative z-index children.
@@ -5301,14 +5302,14 @@ RenderLayer* RenderLayer::hitTestLayer(RenderLayer* rootLayer, RenderLayer* cont
         bool insideFragmentBackgroundRect = false;
         if (hitTestContentsForFragments(layerFragments, request, tempResult, hitTestLocation, HitTestSelf, insideFragmentBackgroundRect)
             && isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) {
-            if (result.isRectBasedTest())
-                result.append(tempResult);
+            if (request.resultIsElementList())
+                result.append(tempResult, request);
             else
                 result = tempResult;
             return this;
         }
-        if (insideFragmentBackgroundRect && result.isRectBasedTest())
-            result.append(tempResult);
+        if (insideFragmentBackgroundRect && request.resultIsElementList())
+            result.append(tempResult, request);
     }
 
     return nullptr;
@@ -5423,7 +5424,7 @@ bool RenderLayer::hitTestContents(const HitTestRequest& request, HitTestResult&
     if (!renderer().hitTest(request, result, hitTestLocation, toLayoutPoint(layerBounds.location() - renderBoxLocation()), hitTestFilter)) {
         // It's wrong to set innerNode, but then claim that you didn't hit anything, unless it is
         // a rect-based test.
-        ASSERT(!result.innerNode() || (result.isRectBasedTest() && result.rectBasedTestResult().size()));
+        ASSERT(!result.innerNode() || (request.resultIsElementList() && result.listBasedTestResult().size()));
         return false;
     }
 
@@ -5472,14 +5473,15 @@ RenderLayer* RenderLayer::hitTestList(Vector<RenderLayer*>* list, RenderLayer* r
         HitTestResult tempResult(result.hitTestLocation());
         hitLayer = childLayer->hitTestLayer(rootLayer, this, request, tempResult, hitTestRect, hitTestLocation, false, transformState, zOffsetForDescendants);
 
-        // If it a rect-based test, we can safely append the temporary result since it might had hit
+        // If it is a list-based test, we can safely append the temporary result since it might had hit
         // nodes but not necesserily had hitLayer set.
-        if (result.isRectBasedTest())
-            result.append(tempResult);
+        ASSERT(!result.isRectBasedTest() || request.resultIsElementList());
+        if (request.resultIsElementList())
+            result.append(tempResult, request);
 
         if (isHitCandidate(hitLayer, depthSortDescendants, zOffset, unflattenedTransformState)) {
             resultLayer = hitLayer;
-            if (!result.isRectBasedTest())
+            if (!request.resultIsElementList())
                 result = tempResult;
             if (!depthSortDescendants)
                 break;
@@ -7220,11 +7222,11 @@ RenderLayer* RenderLayer::hitTestFlowThreadIfRegionForFragments(const LayerFragm
 
         HitTestResult tempResult(result.hitTestLocation());
         RenderLayer* hitLayer = flowThread->layer()->hitTestLayer(flowThread->layer(), nullptr, newRequest, tempResult, hitTestRectInFlowThread, newHitTestLocation, false, transformState, zOffsetForDescendants);
-        if (result.isRectBasedTest())
-            result.append(tempResult);
+        if (request.resultIsElementList())
+            result.append(tempResult, request);
         if (isHitCandidate(hitLayer, depthSortDescendants, zOffset, unflattenedTransformState)) {
             resultLayer = hitLayer;
-            if (!result.isRectBasedTest())
+            if (!request.resultIsElementList())
                 result = tempResult;
             if (!depthSortDescendants)
                 break;
index 76c8059e7f292183969ead32ba61af5f966e7761..b3a84c04f99cd599faee08bfb7250ce0bf136557 100644 (file)
@@ -1575,7 +1575,7 @@ bool RenderTable::nodeAtPoint(const HitTestRequest& request, HitTestResult& resu
     LayoutRect boundsRect(adjustedLocation, size());
     if (visibleToHitTesting() && (action == HitTestBlockBackground || action == HitTestChildBlockBackground) && locationInContainer.intersects(boundsRect)) {
         updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(adjustedLocation)));
-        if (!result.addNodeToRectBasedTestResult(element(), request, locationInContainer, boundsRect))
+        if (result.addNodeToListBasedTestResult(element(), request, locationInContainer, boundsRect) == HitTestProgress::Stop)
             return true;
     }
 
index b02fbc65f7b2cb208b99bfb934cbed445a1ab22d..18afcdbd52a2abf37abd659002c41da006be1da1 100644 (file)
@@ -1548,10 +1548,10 @@ bool RenderTableSection::nodeAtPoint(const HitTestRequest& request, HitTestResul
                     return true;
                 }
             }
-            if (!result.isRectBasedTest())
+            if (!request.resultIsElementList())
                 break;
         }
-        if (!result.isRectBasedTest())
+        if (!request.resultIsElementList())
             break;
     }
 
index 8c1e7d4ea9795555dc717cb619a14eec11ded908..4ce72ce6daa9127764d9f89d40d44406babf8566 100644 (file)
@@ -372,8 +372,8 @@ bool RenderWidget::nodeAtPoint(const HitTestRequest& request, HitTestResult& res
 
         bool isInsideChildFrame = childRoot.hitTest(newHitTestRequest, newHitTestLocation, childFrameResult);
 
-        if (newHitTestLocation.isRectBasedTest())
-            result.append(childFrameResult);
+        if (request.resultIsElementList())
+            result.append(childFrameResult, request);
         else if (isInsideChildFrame)
             result = childFrameResult;
 
index 913573d089dda83975e36ffa0165d830bece0b4a..831bf357cf4d7557fc9b671c5ac3d50acf63981e 100644 (file)
@@ -151,7 +151,7 @@ bool hitTestFlow(const RenderBlockFlow& flow, const Layout& layout, const HitTes
         if (!locationInContainer.intersects(lineRect))
             continue;
         renderer.updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset));
-        if (!result.addNodeToRectBasedTestResult(renderer.node(), request, locationInContainer, lineRect))
+        if (result.addNodeToListBasedTestResult(renderer.node(), request, locationInContainer, lineRect) == HitTestProgress::Stop)
             return true;
     }
     return false;
index ccdf24f9c61e62d59a5bb85b7ecaab7ca9ae7479..83e918e1c1977b842989cbc223493434cd6db283 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "GraphicsContext.h"
 #include "HitTestRequest.h"
+#include "HitTestResult.h"
 #include "LayoutRepainter.h"
 #include "RenderIterator.h"
 #include "RenderSVGResourceFilter.h"
@@ -181,14 +182,16 @@ bool RenderSVGContainer::nodeAtFloatPoint(const HitTestRequest& request, HitTest
     for (RenderObject* child = lastChild(); child; child = child->previousSibling()) {
         if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) {
             updateHitTestResult(result, LayoutPoint(localPoint));
-            return true;
+            if (result.addNodeToListBasedTestResult(child->node(), request, localPoint) == HitTestProgress::Stop)
+                return true;
         }
     }
 
     // Accessibility wants to return SVG containers, if appropriate.
     if (request.type() & HitTestRequest::AccessibilityHitTest && m_objectBoundingBox.contains(localPoint)) {
         updateHitTestResult(result, LayoutPoint(localPoint));
-        return true;
+        if (result.addNodeToListBasedTestResult(&element(), request, localPoint) == HitTestProgress::Stop)
+            return true;
     }
     
     // Spec: Only graphical elements can be targeted by the mouse, period.
index df717c20661c7db4d910456c847f41fd15a9cf73..ce9bd4ac351f8bc0efb7dcfa160c2ce8145767fc 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "FloatQuad.h"
 #include "GraphicsContext.h"
+#include "HitTestResult.h"
 #include "LayoutRepainter.h"
 #include "PointerEventsHitRules.h"
 #include "RenderImageResource.h"
@@ -198,7 +199,8 @@ bool RenderSVGImage::nodeAtFloatPoint(const HitTestRequest& request, HitTestResu
         if (hitRules.canHitFill) {
             if (m_objectBoundingBox.contains(localPoint)) {
                 updateHitTestResult(result, LayoutPoint(localPoint));
-                return true;
+                if (result.addNodeToListBasedTestResult(&imageElement(), request, localPoint) == HitTestProgress::Stop)
+                    return true;
             }
         }
     }
index 0ddbbbcac8b49c54bcc37e9bcbe74092a19b64fa..1a324b72c68056954343148fade78dc8d62c9e22 100644 (file)
@@ -420,7 +420,7 @@ bool RenderSVGRoot::nodeAtPoint(const HitTestRequest& request, HitTestResult& re
             // FIXME: nodeAtFloatPoint() doesn't handle rect-based hit tests yet.
             if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) {
                 updateHitTestResult(result, pointInBorderBox);
-                if (!result.addNodeToRectBasedTestResult(child->node(), request, locationInContainer))
+                if (result.addNodeToListBasedTestResult(child->node(), request, locationInContainer) == HitTestProgress::Stop)
                     return true;
             }
         }
@@ -435,7 +435,7 @@ bool RenderSVGRoot::nodeAtPoint(const HitTestRequest& request, HitTestResult& re
         LayoutRect boundsRect(accumulatedOffset + location(), size());
         if (locationInContainer.intersects(boundsRect)) {
             updateHitTestResult(result, pointInBorderBox);
-            if (!result.addNodeToRectBasedTestResult(&svgSVGElement(), request, locationInContainer, boundsRect))
+            if (result.addNodeToListBasedTestResult(&svgSVGElement(), request, locationInContainer, boundsRect) == HitTestProgress::Stop)
                 return true;
         }
     }
index c353a5b3bc5c61f7a6bc87ab35378f0f9c9ccfef..d86e78f8d6166992703416dc0a5add8ff4548f39 100644 (file)
@@ -32,6 +32,7 @@
 #include "FloatQuad.h"
 #include "GraphicsContext.h"
 #include "HitTestRequest.h"
+#include "HitTestResult.h"
 #include "LayoutRepainter.h"
 #include "PointerEventsHitRules.h"
 #include "RenderSVGResourceMarker.h"
@@ -353,7 +354,8 @@ bool RenderSVGShape::nodeAtFloatPoint(const HitTestRequest& request, HitTestResu
         if ((hitRules.canHitStroke && (svgStyle.hasStroke() || !hitRules.requireStroke) && strokeContains(localPoint, hitRules.requireStroke))
             || (hitRules.canHitFill && (svgStyle.hasFill() || !hitRules.requireFill) && fillContains(localPoint, hitRules.requireFill, fillRule))) {
             updateHitTestResult(result, LayoutPoint(localPoint));
-            return true;
+            if (result.addNodeToListBasedTestResult(&graphicsElement(), request, localPoint) == HitTestProgress::Stop)
+                return true;
         }
     }
     return false;
index 99aa4a1afced9cc3d326f33a70b272133649c0cf..68a929c7c23f41b2c8250e94d07b80707896fbdc 100644 (file)
@@ -671,7 +671,7 @@ bool SVGInlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult&
                     
                     if (fragmentQuad.containsPoint(locationInContainer.point())) {
                         renderer().updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset));
-                        if (!result.addNodeToRectBasedTestResult(&renderer().textNode(), request, locationInContainer, rect))
+                        if (result.addNodeToListBasedTestResult(&renderer().textNode(), request, locationInContainer, rect) == HitTestProgress::Stop)
                             return true;
                     }
                 }
index 367913418a413087821f06951e368e7edf356b0a..ee2f91f14de603bf86955dc597d05d614f4c3984 100644 (file)
@@ -1829,7 +1829,7 @@ ExceptionOr<RefPtr<NodeList>> Internals::nodesFromRect(Document& document, int c
     float zoomFactor = frame->pageZoomFactor();
     LayoutPoint point(centerX * zoomFactor + frameView->scrollX(), centerY * zoomFactor + frameView->scrollY());
 
-    HitTestRequest::HitTestRequestType hitType = HitTestRequest::ReadOnly | HitTestRequest::Active;
+    HitTestRequest::HitTestRequestType hitType = HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::CollectMultipleElements;
     if (ignoreClipping)
         hitType |= HitTestRequest::IgnoreClipping;
     if (!allowUserAgentShadowContent)
@@ -1843,24 +1843,13 @@ ExceptionOr<RefPtr<NodeList>> Internals::nodesFromRect(Document& document, int c
     if (!request.ignoreClipping() && !frameView->visibleContentRect().intersects(HitTestLocation::rectForPoint(point, topPadding, rightPadding, bottomPadding, leftPadding)))
         return nullptr;
 
+    HitTestResult result(point, topPadding, rightPadding, bottomPadding, leftPadding);
+    renderView->hitTest(request, result);
+    const HitTestResult::NodeSet& nodeSet = result.listBasedTestResult();
     Vector<Ref<Node>> matches;
-
-    // Need padding to trigger a rect based hit test, but we want to return a NodeList
-    // so we special case this.
-    if (!topPadding && !rightPadding && !bottomPadding && !leftPadding) {
-        HitTestResult result(point);
-        renderView->hitTest(request, result);
-        if (result.innerNode())
-            matches.append(*result.innerNode()->deprecatedShadowAncestorNode());
-    } else {
-        HitTestResult result(point, topPadding, rightPadding, bottomPadding, leftPadding);
-        renderView->hitTest(request, result);
-
-        const HitTestResult::NodeSet& nodeSet = result.rectBasedTestResult();
-        matches.reserveInitialCapacity(nodeSet.size());
-        for (auto& node : nodeSet)
-            matches.uncheckedAppend(*node);
-    }
+    matches.reserveInitialCapacity(nodeSet.size());
+    for (auto& node : nodeSet)
+        matches.uncheckedAppend(*node);
 
     return RefPtr<NodeList> { StaticNodeList::create(WTFMove(matches)) };
 }