Skip to content

Commit

Permalink
ShadowRoot interface should have elementFromPoint
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=162882

Reviewed by Chris Dumez.

Source/WebCore:

Add elementFromPoint to ShadowRoot's prototype as specified at:
https://www.w3.org/TR/shadow-dom/#extensions-to-the-documentorshadowroot-mixin
with changes proposed at w3c/csswg-drafts#556

Added TreeScope::retargetToScope which implements

This patch also factors DocumentOrShadowRoot.idl out of Document and ShadowRoot interfaces to better match
the latest DOM specification: https://dom.spec.whatwg.org/#mixin-documentorshadowroot

Test: fast/shadow-dom/Document-prototype-elementFromPoint.html

* CMakeLists.txt:
* DerivedSources.make:
* WebCore.xcodeproj/project.pbxproj:
* dom/Document.cpp:
(WebCore::Document::nodeFromPoint): Moved to TreeScope.
(WebCore::Document::elementFromPoint): Moved to TreeScope.
* dom/Document.h:
* dom/Document.idl: Moved elementFromPoint and activeElement to DocumentOrShadowRoot.idl.
* dom/DocumentOrShadowRoot.idl: Added.
* dom/EventPath.cpp:
(WebCore::RelatedNodeRetargeter::checkConsistency): Use newly added TreeScope::retargetToScope.
* dom/ShadowRoot.idl: Moved activeElement to DocumentOrShadowRoot.idl.
* dom/TreeScope.cpp:
(WebCore::TreeScope::retargetToScope): Added. Implements https://dom.spec.whatwg.org/#retarget efficiently.
Instead of checking whether A (node) is a shadow-including inclusive ancestor of B (this scope) at each
parent, find the lowest ancestor which contains both A and B, and return the self-inclusive ancestor of B
in that tree. To find the lowest common ancestor in O(n), traverse all ancestors of A and B separately and
do a top-down traversal. The last tree scope in which A's ancestor and B's ancestor match is the lowest
common ancestor.
(WebCore::TreeScope::nodeFromPoint): Moved from Document.
(WebCore::TreeScope::elementFromPoint): Moved from Document. Use retargetToScope and parentInComposedTree
instead of parentNode and ancestorInThisScope to match the semantics proposed in
w3c/csswg-drafts#556
* dom/TreeScope.h:

LayoutTests:

Add a W3C style testharness.js test for elementFromPoint on ShadowRoot.

* fast/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint-expected.txt: Added.
* fast/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@206795 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
rniwa@webkit.org committed Oct 4, 2016
1 parent ade1810 commit 42a0769
Show file tree
Hide file tree
Showing 16 changed files with 433 additions and 66 deletions.
12 changes: 12 additions & 0 deletions LayoutTests/ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
2016-10-03 Ryosuke Niwa <rniwa@webkit.org>

ShadowRoot interface should have elementFromPoint
https://bugs.webkit.org/show_bug.cgi?id=162882

Reviewed by Chris Dumez.

Add a W3C style testharness.js test for elementFromPoint on ShadowRoot.

* fast/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint-expected.txt: Added.
* fast/shadow-dom/DocumentOrShadowRoot-prototype-elementFromPoint.html: Added.

2016-10-04 Myles C. Maxfield <mmaxfield@apple.com>

font-family: cursive should map to KaiTi in Chinese
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

PASS document.elementFromPoint must return the shadow host of the hit-tested text node when the host has display: inline
PASS document.elementFromPoint must return the shadow host of the hit-tested text node when the host has display: block
PASS document.elementFromPoint must return the shadow host of the hit-tested text node when the host has display: inline-block
PASS document.elementFromPoint must return the shadow host of the hit-tested text node assigned to a slot when the host has display: inline
PASS document.elementFromPoint must return the shadow host of the hit-tested text node assigned to a slot when the host has display: block
PASS document.elementFromPoint must return the shadow host of the hit-tested text node assigned to a slot when the host has display: inline-block
PASS document.elementFromPoint must return the element assigned to a slot when the shadow host of the slot has display: inline
PASS document.elementFromPoint must return the element assigned to a slot when the shadow host of the slot has display: block
PASS document.elementFromPoint must return the element assigned to a slot when the shadow host of the slot has display: inline-block
PASS shadowRoot.elementFromPoint must return the element parent of the hit-tested text node under the point when the shadow host has display: inline
PASS shadowRoot.elementFromPoint must return the element parent of the hit-tested text node under the point when the shadow host has display: block
PASS shadowRoot.elementFromPoint must return the element parent of the hit-tested text node under the point when the shadow host has display: inline-block
PASS shadowRoot.elementFromPoint must return the shadow host when the hit-tested text node is a direct child of the root and the host has display: inline
PASS shadowRoot.elementFromPoint must return the shadow host when the hit-tested text node is a direct child of the root and the host has display: block
PASS shadowRoot.elementFromPoint must return the shadow host when the hit-tested text node is a direct child of the root and the host has display: inline-block
PASS shadowRoot.elementFromPoint must return the slot to which the hit-tested text node is assigned when its host has display: inline
PASS shadowRoot.elementFromPoint must return the slot to which the hit-tested text node is assigned when its host has display: block
PASS shadowRoot.elementFromPoint must return the slot to which the hit-tested text node is assigned when its host has display: inline-block
PASS shadowRoot.elementFromPoint must return the element parent of the hit-tested text node assigned to a slot in the shadow tree when its host has display: inline
PASS shadowRoot.elementFromPoint must return the element parent of the hit-tested text node assigned to a slot in the shadow tree when its host has display: block
PASS shadowRoot.elementFromPoint must return the element parent of the hit-tested text node assigned to a slot in the shadow tree when its host has display: inline-block
PASS shadowRoot.elementFromPoint must return a child element assigned to a slot when the hit-tested text node is assigned to the shadow tree of the child element and the outer shadow host has display: inline
PASS shadowRoot.elementFromPoint must return a child element assigned to a slot when the hit-tested text node is assigned to the shadow tree of the child element and the outer shadow host has display: block
PASS shadowRoot.elementFromPoint must return a child element assigned to a slot when the hit-tested text node is assigned to the shadow tree of the child element and the outer shadow host has display: inline-block
PASS shadowRoot.elementFromPoint must return a child element assigned to a slot when the hit-tested text node is assigned to a slot in the shadow tree of the child element and the outer shadow host has display: inline
PASS shadowRoot.elementFromPoint must return a child element assigned to a slot when the hit-tested text node is assigned to a slot in the shadow tree of the child element and the outer shadow host has display: block
PASS shadowRoot.elementFromPoint must return a child element assigned to a slot when the hit-tested text node is assigned to a slot in the shadow tree of the child element and the outer shadow host has display: inline-block
PASS shadowRoot.elementFromPoint must return a child element with its own shadow tree assigned to a slot when the hit-tested text node is its direct child and the outer shadow host has display: inline
PASS shadowRoot.elementFromPoint must return a child element with its own shadow tree assigned to a slot when the hit-tested text node is its direct child and the outer shadow host has display: block
PASS shadowRoot.elementFromPoint must return a child element with its own shadow tree assigned to a slot when the hit-tested text node is its direct child and the outer shadow host has display: inline-block
PASS shadowRoot.elementFromPoint must return a child element with its own shadow tree assigned to a slot when the hit-tested text node is a child of another element and the outer shadow host has display: inline
PASS shadowRoot.elementFromPoint must return a child element with its own shadow tree assigned to a slot when the hit-tested text node is a child of another element and the outer shadow host has display: block
PASS shadowRoot.elementFromPoint must return a child element with its own shadow tree assigned to a slot when the hit-tested text node is a child of another element and the outer shadow host has display: inline-block

Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
<!DOCTYPE html>
<html>
<head>
<title>Shadow DOM and CSSOM View: Document.prototype.elementFromPoint</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<meta name="assert" content="DocumentOrShadowRoot must have elementFromPoint and must return retarget the result against the context object.">
<link rel="help" href="https://www.w3.org/TR/cssom-view-1/#dom-document-elementfrompoint">
<link rel="help" href="https://www.w3.org/TR/shadow-dom/#extensions-to-the-documentorshadowroot-mixin">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
</head>
<body>
<div id="container"></div>
<style>

container { position: relative; }
test-element { display: block; width: 100px; height: 100px; }

</style>
<script>

function pointInElement(node) {
let x = 5;
let y = 5;
do {
x += node.offsetLeft;
y += node.offsetTop;
node = node.offsetParent;
} while (node);
return [x, y];
}

const displayValues = ['inline', 'block', 'inline-block'];
var container = document.getElementById('container');

displayValues.forEach(function (displayValue) {
test(function () {
container.innerHTML = '';
let host = document.createElement('test-element');
host.style.display = displayValue;
let shadow = host.attachShadow({mode: 'closed'});
shadow.innerHTML = 'hello';
container.appendChild(host);
assert_equals(document.elementFromPoint(...pointInElement(host)), host);
}, 'document.elementFromPoint must return the shadow host of the hit-tested text node when the host has display: ' + displayValue);
});

displayValues.forEach(function (displayValue) {
test(function () {
container.innerHTML = '';
let host = document.createElement('test-element');
host.style.display = displayValue;
let shadow = host.attachShadow({mode: 'closed'});
shadow.innerHTML = '<slot></slot>';
host.innerHTML = 'text';
container.appendChild(host);
assert_equals(document.elementFromPoint(...pointInElement(host)), host);
}, 'document.elementFromPoint must return the shadow host of the hit-tested text node assigned to a slot when the host has display: ' + displayValue);
});

displayValues.forEach(function (displayValue) {
test(function () {
container.innerHTML = '';
let host = document.createElement('test-element');
host.style.display = displayValue;
let shadow = host.attachShadow({mode: 'closed'});
shadow.innerHTML = '<slot></slot>';
host.innerHTML = '<span>text</span>';
container.appendChild(host);
assert_equals(document.elementFromPoint(...pointInElement(host)), host.querySelector('span'));
}, 'document.elementFromPoint must return the element assigned to a slot when the shadow host of the slot has display: ' + displayValue);
});

displayValues.forEach(function (displayValue) {
test(function () {
container.innerHTML = '';
let host = document.createElement('test-element');
host.style.display = displayValue;
let shadow = host.attachShadow({mode: 'closed'});
shadow.innerHTML = '<span>text</span>';
container.appendChild(host);
assert_equals(shadow.elementFromPoint(...pointInElement(shadow.querySelector('span'))), shadow.querySelector('span'));
}, 'shadowRoot.elementFromPoint must return the element parent of the hit-tested text node under the point when the shadow host has display: ' + displayValue);
});

displayValues.forEach(function (displayValue) {
test(function () {
container.innerHTML = '';
let host = document.createElement('test-element');
host.style.display = displayValue;
let shadow = host.attachShadow({mode: 'closed'});
shadow.innerHTML = 'text';
container.appendChild(host);
assert_equals(shadow.elementFromPoint(...pointInElement(host)), host);
}, 'shadowRoot.elementFromPoint must return the shadow host when the hit-tested text node is a direct child of the root and the host has display: ' + displayValue);
});

displayValues.forEach(function (displayValue) {
test(function () {
container.innerHTML = '';
let host = document.createElement('test-element');
host.style.display = displayValue;
let shadow = host.attachShadow({mode: 'closed'});
shadow.innerHTML = '<slot></slot>';
host.innerHTML = 'hello';
container.appendChild(host);
assert_equals(shadow.elementFromPoint(...pointInElement(host)), shadow.querySelector('slot'));
}, 'shadowRoot.elementFromPoint must return the slot to which the hit-tested text node is assigned when its host has display: ' + displayValue);
});

displayValues.forEach(function (displayValue) {
test(function () {
container.innerHTML = '';
let host = document.createElement('test-element');
host.style.display = displayValue;
let shadow = host.attachShadow({mode: 'closed'});
shadow.innerHTML = '<slot></slot>';
host.innerHTML = '<span>hello</span>';
container.appendChild(host);
assert_equals(shadow.elementFromPoint(...pointInElement(host)), host.querySelector('span'));
}, 'shadowRoot.elementFromPoint must return the element parent of the hit-tested text node assigned to a slot in the shadow tree when its host has display: ' + displayValue);
});

displayValues.forEach(function (displayValue) {
test(function () {
container.innerHTML = '';
let host = document.createElement('test-element');
host.style.display = displayValue;
let shadow = host.attachShadow({mode: 'closed'});
shadow.innerHTML = '<slot></slot>';
host.innerHTML = '<inner-host>hello</inner-host>';
container.appendChild(host);

let innerHost = host.querySelector('inner-host');
let innerShadow = innerHost.attachShadow({mode: 'closed'});
innerShadow.innerHTML = '<slot></slot>';

assert_equals(shadow.elementFromPoint(...pointInElement(host)), innerHost);
}, 'shadowRoot.elementFromPoint must return a child element assigned to a slot'
+ ' when the hit-tested text node is assigned to the shadow tree of the child element and the outer shadow host has display: ' + displayValue);
});

displayValues.forEach(function (displayValue) {
test(function () {
container.innerHTML = '';
let host = document.createElement('test-element');
host.style.display = displayValue;
let shadow = host.attachShadow({mode: 'closed'});
shadow.innerHTML = '<slot></slot>';
host.innerHTML = '<inner-host>hello</inner-host>';
container.appendChild(host);

let innerHost = host.querySelector('inner-host');
let innerShadow = innerHost.attachShadow({mode: 'closed'});
innerShadow.innerHTML = '<slot></slot>';

assert_equals(shadow.elementFromPoint(...pointInElement(host)), innerHost);
}, 'shadowRoot.elementFromPoint must return a child element assigned to a slot'
+ ' when the hit-tested text node is assigned to a slot in the shadow tree of the child element and the outer shadow host has display: ' + displayValue);
});

displayValues.forEach(function (displayValue) {
test(function () {
container.innerHTML = '';
let host = document.createElement('test-element');
host.style.display = displayValue;
let shadow = host.attachShadow({mode: 'closed'});
shadow.innerHTML = '<slot></slot>';
host.innerHTML = '<inner-host></inner-host>';
container.appendChild(host);

let innerHost = host.querySelector('inner-host');
let innerShadow = innerHost.attachShadow({mode: 'closed'});
innerShadow.innerHTML = 'hello';

assert_equals(shadow.elementFromPoint(...pointInElement(host)), innerHost);
}, 'shadowRoot.elementFromPoint must return a child element with its own shadow tree assigned to a slot'
+ ' when the hit-tested text node is its direct child and the outer shadow host has display: ' + displayValue);
});

displayValues.forEach(function (displayValue) {
test(function () {
container.innerHTML = '';
let host = document.createElement('test-element');
host.style.display = displayValue;
let shadow = host.attachShadow({mode: 'closed'});
shadow.innerHTML = '<slot></slot>';
host.innerHTML = '<inner-host></inner-host>';
container.appendChild(host);

let innerHost = host.querySelector('inner-host');
let innerShadow = innerHost.attachShadow({mode: 'closed'});
innerShadow.innerHTML = '<span>hello</span>';

assert_equals(shadow.elementFromPoint(...pointInElement(host)), innerHost);
}, 'shadowRoot.elementFromPoint must return a child element with its own shadow tree assigned to a slot'
+ ' when the hit-tested text node is a child of another element and the outer shadow host has display: ' + displayValue);
});

container.innerHTML = '';

</script>
</body>
</html>
1 change: 1 addition & 0 deletions Source/WebCore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ set(WebCore_NON_SVG_IDL_FILES
dom/DeviceOrientationEvent.idl
dom/Document.idl
dom/DocumentFragment.idl
dom/DocumentOrShadowRoot.idl
dom/DocumentType.idl
dom/Element.idl
dom/ErrorEvent.idl
Expand Down
43 changes: 43 additions & 0 deletions Source/WebCore/ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
2016-10-03 Ryosuke Niwa <rniwa@webkit.org>

ShadowRoot interface should have elementFromPoint
https://bugs.webkit.org/show_bug.cgi?id=162882

Reviewed by Chris Dumez.

Add elementFromPoint to ShadowRoot's prototype as specified at:
https://www.w3.org/TR/shadow-dom/#extensions-to-the-documentorshadowroot-mixin
with changes proposed at https://github.com/w3c/csswg-drafts/issues/556

Added TreeScope::retargetToScope which implements

This patch also factors DocumentOrShadowRoot.idl out of Document and ShadowRoot interfaces to better match
the latest DOM specification: https://dom.spec.whatwg.org/#mixin-documentorshadowroot

Test: fast/shadow-dom/Document-prototype-elementFromPoint.html

* CMakeLists.txt:
* DerivedSources.make:
* WebCore.xcodeproj/project.pbxproj:
* dom/Document.cpp:
(WebCore::Document::nodeFromPoint): Moved to TreeScope.
(WebCore::Document::elementFromPoint): Moved to TreeScope.
* dom/Document.h:
* dom/Document.idl: Moved elementFromPoint and activeElement to DocumentOrShadowRoot.idl.
* dom/DocumentOrShadowRoot.idl: Added.
* dom/EventPath.cpp:
(WebCore::RelatedNodeRetargeter::checkConsistency): Use newly added TreeScope::retargetToScope.
* dom/ShadowRoot.idl: Moved activeElement to DocumentOrShadowRoot.idl.
* dom/TreeScope.cpp:
(WebCore::TreeScope::retargetToScope): Added. Implements https://dom.spec.whatwg.org/#retarget efficiently.
Instead of checking whether A (node) is a shadow-including inclusive ancestor of B (this scope) at each
parent, find the lowest ancestor which contains both A and B, and return the self-inclusive ancestor of B
in that tree. To find the lowest common ancestor in O(n), traverse all ancestors of A and B separately and
do a top-down traversal. The last tree scope in which A's ancestor and B's ancestor match is the lowest
common ancestor.
(WebCore::TreeScope::nodeFromPoint): Moved from Document.
(WebCore::TreeScope::elementFromPoint): Moved from Document. Use retargetToScope and parentInComposedTree
instead of parentNode and ancestorInThisScope to match the semantics proposed in
https://github.com/w3c/csswg-drafts/issues/556
* dom/TreeScope.h:

2016-10-04 Myles C. Maxfield <mmaxfield@apple.com>

font-family: cursive should map to KaiTi in Chinese
Expand Down
1 change: 1 addition & 0 deletions Source/WebCore/DerivedSources.make
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ JS_BINDING_IDLS = \
$(WebCore)/dom/DeviceOrientationEvent.idl \
$(WebCore)/dom/Document.idl \
$(WebCore)/dom/DocumentFragment.idl \
$(WebCore)/dom/DocumentOrShadowRoot.idl \
$(WebCore)/dom/DocumentType.idl \
$(WebCore)/dom/Element.idl \
$(WebCore)/dom/ErrorEvent.idl \
Expand Down
Loading

0 comments on commit 42a0769

Please sign in to comment.