WebKit Bugzilla
Attachment 357294 Details for
Bug 157437
: offsetLeft and offsetParent should adjust across shadow boundaries
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
WIP
wip157437.patch (text/plain), 20.88 KB, created by
Ryosuke Niwa
on 2018-12-13 20:20:30 PST
(
hide
)
Description:
WIP
Filename:
MIME Type:
Creator:
Ryosuke Niwa
Created:
2018-12-13 20:20:30 PST
Size:
20.88 KB
patch
obsolete
>Index: LayoutTests/fast/shadow-dom/offsetParent-across-shadow-boundaries-expected.txt >=================================================================== >--- LayoutTests/fast/shadow-dom/offsetParent-across-shadow-boundaries-expected.txt (nonexistent) >+++ LayoutTests/fast/shadow-dom/offsetParent-across-shadow-boundaries-expected.txt (working copy) >@@ -0,0 +1,18 @@ >+ >+PASS offsetParent must return the offset parent in the same shadow tree of open mode >+PASS offsetParent must return the offset parent in the same shadow tree of closed mode >+PASS offsetParent must return the offset parent in the same shadow tree of open mode even when nested >+PASS offsetParent must return the offset parent in the same shadow tree of closed mode even when nested >+PASS offsetParent must skip offset parents of an element when the context object is assigned to a slot in a shadow tree of open mode >+PASS offsetParent must skip offset parents of an element when the context object is assigned to a slot in a shadow tree of closed mode >+PASS offsetParent must skip offset parents of an element when the context object is assigned to a slot in a shadow tree of open mode >+PASS offsetParent must skip offset parents of an element when the context object is assigned to a slot in a shadow tree of closed mode >+PASS offsetParent must skip offset parents of an element when the context object is assigned to a slot in nested shadow trees of open mode >+PASS offsetParent must skip offset parents of an element when the context object is assigned to a slot in nested shadow trees of closed mode >+FAIL offsetParent must find the first offset parent which is a shadow-including ancestor of the context object even some shadow tree of open mode did not have any offset parent assert_equals: expected 24 but got 32 >+FAIL offsetParent must find the first offset parent which is a shadow-including ancestor of the context object even some shadow tree of closed mode did not have any offset parent assert_equals: expected 24 but got 32 >+PASS offsetParent must return null on a child element of a shadow host for the shadow tree in open mode which is not assigned to any slot >+PASS offsetParent must return null on a child element of a shadow host for the shadow tree in closed mode which is not assigned to any slot >+PASS offsetParent must return null on a child element of a shadow host for the shadow tree in open mode which is not in the flat tree >+PASS offsetParent must return null on a child element of a shadow host for the shadow tree in closed mode which is not in the flat tree >+ >Index: LayoutTests/fast/shadow-dom/offsetParent-across-shadow-boundaries.html >=================================================================== >--- LayoutTests/fast/shadow-dom/offsetParent-across-shadow-boundaries.html (nonexistent) >+++ LayoutTests/fast/shadow-dom/offsetParent-across-shadow-boundaries.html (working copy) >@@ -0,0 +1,190 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> >+<meta name="assert" content="offsetParent should only return nodes that are shadow including ancestor"> >+<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetparent"> >+<link rel="help" href="https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor"> >+<script src="../../resources/testharness.js"></script> >+<script src="../../resources/testharnessreport.js"></script> >+<script src="resources/event-path-test-helpers.js"></script> >+</head> >+<body> >+<div id="log"></div> >+<div id="container" style="position: relative"></div> >+<script> >+ >+const container = document.getElementById('container'); >+ >+function testOffsetParentInShadowTree(mode) { >+ test(function () { >+ const host = document.createElement('div'); >+ container.appendChild(host); >+ this.add_cleanup(() => host.remove()); >+ const shadowRoot = host.attachShadow({mode}); >+ shadowRoot.innerHTML = '<div id="relativeParent" style="position: relative; padding-left: 100px; padding-top: 70px;"><div id="target"></div></div>'; >+ const relativeParent = shadowRoot.getElementById('relativeParent'); >+ >+ assert_true(relativeParent instanceof HTMLDivElement); >+ const target = shadowRoot.getElementById('target'); >+ assert_equals(target.offsetParent, relativeParent); >+ assert_equals(target.offsetLeft, 100); >+ assert_equals(target.offsetTop, 70); >+ }, `offsetParent must return the offset parent in the same shadow tree of ${mode} mode`); >+} >+ >+testOffsetParentInShadowTree('open'); >+testOffsetParentInShadowTree('closed'); >+ >+function testOffsetParentInNestedShadowTrees(mode) { >+ test(function () { >+ const outerHost = document.createElement('section'); >+ container.appendChild(outerHost); >+ this.add_cleanup(() => outerHost.remove()); >+ const outerShadow = outerHost.attachShadow({mode}); >+ outerShadow.innerHTML = '<section id="outerParent" style="position: absolute; top: 50px; left: 50px;"></section>'; >+ >+ const innerHost = document.createElement('div'); >+ outerShadow.firstChild.appendChild(innerHost); >+ const innerShadow = innerHost.attachShadow({mode}); >+ innerShadow.innerHTML = '<div id="innerParent" style="position: relative; padding-left: 60px; padding-top: 40px;"><div id="target"></div></div>'; >+ const innerParent = innerShadow.getElementById('innerParent'); >+ >+ const target = innerShadow.getElementById('target'); >+ assert_true(innerParent instanceof HTMLDivElement); >+ assert_equals(target.offsetParent, innerParent); >+ assert_equals(target.offsetLeft, 60); >+ assert_equals(target.offsetTop, 40); >+ >+ outerHost.remove(); >+ }, `offsetParent must return the offset parent in the same shadow tree of ${mode} mode even when nested`); >+} >+ >+testOffsetParentInNestedShadowTrees('open'); >+testOffsetParentInNestedShadowTrees('closed'); >+ >+function testOffsetParentOnElementAssignedToSlotInsideOffsetParent(mode) { >+ test(function () { >+ const host = document.createElement('div'); >+ host.innerHTML = '<div id="target"></div>' >+ container.appendChild(host); >+ this.add_cleanup(() => host.remove()); >+ const shadowRoot = host.attachShadow({mode}); >+ shadowRoot.innerHTML = '<div style="position: relative; padding-left: 85px; padding-top: 45px;"><slot></slot></div>'; >+ const target = host.querySelector('#target'); >+ assert_equals(target.offsetParent, container); >+ assert_equals(target.offsetLeft, 85); >+ assert_equals(target.offsetTop, 45); >+ }, `offsetParent must skip offset parents of an element when the context object is assigned to a slot in a shadow tree of ${mode} mode`); >+} >+ >+testOffsetParentOnElementAssignedToSlotInsideOffsetParent('open'); >+testOffsetParentOnElementAssignedToSlotInsideOffsetParent('closed'); >+ >+function testOffsetParentOnElementAssignedToSlotInsideNestedOffsetParents(mode) { >+ test(function () { >+ const host = document.createElement('div'); >+ host.innerHTML = '<div id="target" style="border:solid 1px blue;">hi</div>'; >+ const previousBlock = document.createElement('div'); >+ previousBlock.style.height = '12px'; >+ container.append(previousBlock, host); >+ this.add_cleanup(() => container.innerHTML = ''); >+ const shadowRoot = host.attachShadow({mode}); >+ shadowRoot.innerHTML = '<section style="position: relative; margin-left: 20px; margin-top: 100px; background: #ccc"><div style="position: absolute; top: 10px; left: 10px;"><slot></slot></div></section>'; >+ const target = host.querySelector('#target'); >+ assert_equals(target.offsetParent, container); >+ assert_equals(target.offsetLeft, 30); >+ assert_equals(target.offsetTop, 122); >+ }, `offsetParent must skip offset parents of an element when the context object is assigned to a slot in a shadow tree of ${mode} mode`); >+} >+ >+testOffsetParentOnElementAssignedToSlotInsideNestedOffsetParents('open'); >+testOffsetParentOnElementAssignedToSlotInsideNestedOffsetParents('closed'); >+ >+function testOffsetParentOnElementAssignedToSlotInsideNestedShadowTrees(mode) { >+ test(function () { >+ const outerHost = document.createElement('section'); >+ outerHost.innerHTML = '<div id="target"></div>'; >+ container.appendChild(outerHost); >+ this.add_cleanup(() => outerHost.remove()); >+ const outerShadow = outerHost.attachShadow({mode}); >+ outerShadow.innerHTML = '<section style="position: absolute; top: 40px; left: 50px;"><div id="innerHost"><slot></slot></div></section>'; >+ >+ const innerShadow = outerShadow.getElementById('innerHost').attachShadow({mode}); >+ innerShadow.innerHTML = '<div style="position: absolute; top: 200px; margin-left: 100px;"><slot></slot></div>'; >+ >+ const target = outerHost.querySelector('#target'); >+ assert_equals(target.offsetParent, container); >+ assert_equals(target.offsetLeft, 150); >+ assert_equals(target.offsetTop, 240); >+ outerHost.remove(); >+ }, `offsetParent must skip offset parents of an element when the context object is assigned to a slot in nested shadow trees of ${mode} mode`); >+} >+ >+testOffsetParentOnElementAssignedToSlotInsideNestedShadowTrees('open'); >+testOffsetParentOnElementAssignedToSlotInsideNestedShadowTrees('closed'); >+ >+function testOffsetParentOnElementInsideShadowTreeWithoutOffsetParent(mode) { >+ test(function () { >+ const outerHost = document.createElement('section'); >+ container.appendChild(outerHost); >+ this.add_cleanup(() => outerHost.remove()); >+ const outerShadow = outerHost.attachShadow({mode}); >+ outerShadow.innerHTML = '<div id="innerHost"><div id="target"></div></div>'; >+ >+ const innerShadow = outerShadow.getElementById('innerHost').attachShadow({mode}); >+ innerShadow.innerHTML = '<div style="position: absolute; top: 23px; left: 24px;"><slot></slot></div>'; >+ >+ const target = outerShadow.querySelector('#target'); >+ assert_equals(target.offsetParent, container); >+ assert_equals(target.offsetLeft, 24); >+ assert_equals(target.offsetTop, 23); >+ }, `offsetParent must find the first offset parent which is a shadow-including ancestor of the context object even some shadow tree of ${mode} mode did not have any offset parent`); >+} >+ >+testOffsetParentOnElementInsideShadowTreeWithoutOffsetParent('open'); >+testOffsetParentOnElementInsideShadowTreeWithoutOffsetParent('closed'); >+ >+function testOffsetParentOnUnassignedChild(mode) { >+ test(function () { >+ const host = document.createElement('section'); >+ host.innerHTML = '<div id="target"></div>'; >+ this.add_cleanup(() => host.remove()); >+ container.appendChild(host); >+ const shadowRoot = host.attachShadow({mode}); >+ shadowRoot.innerHTML = '<section style="position: absolute; top: 50px; left: 50px;">content</section>'; >+ const target = host.querySelector('#target'); >+ assert_equals(target.offsetParent, null); >+ assert_equals(target.offsetLeft, 0); >+ assert_equals(target.offsetTop, 0); >+ }, `offsetParent must return null on a child element of a shadow host for the shadow tree in ${mode} mode which is not assigned to any slot`); >+} >+ >+testOffsetParentOnUnassignedChild('open'); >+testOffsetParentOnUnassignedChild('closed'); >+ >+function testOffsetParentOnAssignedChildNotInFlatTree(mode) { >+ test(function () { >+ const outerHost = document.createElement('section'); >+ outerHost.innerHTML = '<div id="target"></div>'; >+ container.appendChild(outerHost); >+ this.add_cleanup(() => outerHost.remove()); >+ const outerShadow = outerHost.attachShadow({mode}); >+ outerShadow.innerHTML = '<div id="innerHost"><div style="position: absolute; top: 50px; left: 50px;"><slot></slot></div></div>'; >+ >+ const innerShadow = outerShadow.getElementById('innerHost').attachShadow({mode}); >+ innerShadow.innerHTML = '<div>content</div>'; >+ >+ const target = outerHost.querySelector('#target'); >+ assert_equals(target.offsetParent, null); >+ assert_equals(target.offsetLeft, 0); >+ assert_equals(target.offsetTop, 0); >+ }, `offsetParent must return null on a child element of a shadow host for the shadow tree in ${mode} mode which is not in the flat tree`); >+} >+ >+testOffsetParentOnAssignedChildNotInFlatTree('open'); >+testOffsetParentOnAssignedChildNotInFlatTree('closed'); >+ >+</script> >+</body> >+</html> >Index: Source/WebCore/dom/Element.cpp >=================================================================== >--- Source/WebCore/dom/Element.cpp (revision 239095) >+++ Source/WebCore/dom/Element.cpp (working copy) >@@ -883,27 +883,67 @@ > return subpixelMetricsEnabled(document) ? value : roundStrategy == Round ? round(value) : floor(value); > } > >+static double adjustOffsetForZoomAndSubpixelLayout(RenderBoxModelObject* renderer, const LayoutUnit& offset) >+{ >+ LayoutUnit offsetLeft = subpixelMetricsEnabled(renderer->document()) ? offset : LayoutUnit(roundToInt(offset)); >+ double zoomFactor = 1; >+ double offsetLeftAdjustedWithZoom = adjustForLocalZoom(offsetLeft, *renderer, zoomFactor); >+ return convertToNonSubpixelValueIfNeeded(offsetLeftAdjustedWithZoom, renderer->document(), zoomFactor == 1 ? Floor : Round); >+} >+ >+double Element::offsetLeftForBindings() >+{ >+ auto parent = makeRefPtr(offsetParent()); >+ if (!parent || !parent->isInShadowTree()) >+ return offsetLeft(); >+ auto retargetedOffsetParent = makeRef(treeScope().retargetElementToScope(*parent)); >+ if (parent.get() == retargetedOffsetParent.ptr()) >+ return offsetLeft(); >+ >+ double offset = offsetLeft(); >+ RefPtr<Element> currentElement = parent; >+ // FIXME: offset parent may skip retargetedOffsetParent's tree scope. >+ while (currentElement && ¤tElement->treeScope() != &retargetedOffsetParent->treeScope()) { >+ offset += currentElement->offsetLeft(); >+ currentElement = currentElement->offsetParent(); >+ } >+ >+ return offset; >+} >+ > double Element::offsetLeft() > { > document().updateLayoutIgnorePendingStylesheets(); >- if (RenderBoxModelObject* renderer = renderBoxModelObject()) { >- LayoutUnit offsetLeft = subpixelMetricsEnabled(renderer->document()) ? renderer->offsetLeft() : LayoutUnit(roundToInt(renderer->offsetLeft())); >- double zoomFactor = 1; >- double offsetLeftAdjustedWithZoom = adjustForLocalZoom(offsetLeft, *renderer, zoomFactor); >- return convertToNonSubpixelValueIfNeeded(offsetLeftAdjustedWithZoom, renderer->document(), zoomFactor == 1 ? Floor : Round); >- } >+ if (RenderBoxModelObject* renderer = renderBoxModelObject()) >+ return adjustOffsetForZoomAndSubpixelLayout(renderer, renderer->offsetLeft()); > return 0; > } > >+double Element::offsetTopForBindings() >+{ >+ auto parent = makeRefPtr(offsetParent()); >+ if (!parent || !parent->isInShadowTree()) >+ return offsetTop(); >+ auto retargetedOffsetParent = makeRef(treeScope().retargetElementToScope(*parent)); >+ if (parent.get() == retargetedOffsetParent.ptr()) >+ return offsetTop(); >+ >+ double offset = offsetTop(); >+ RefPtr<Element> currentElement = parent; >+ // FIXME: offset parent may skip retargetedOffsetParent's tree scope. >+ while (currentElement && ¤tElement->treeScope() != &retargetedOffsetParent->treeScope()) { >+ offset += currentElement->offsetTop(); >+ currentElement = currentElement->offsetParent(); >+ } >+ >+ return offset; >+} >+ > double Element::offsetTop() > { > document().updateLayoutIgnorePendingStylesheets(); >- if (RenderBoxModelObject* renderer = renderBoxModelObject()) { >- LayoutUnit offsetTop = subpixelMetricsEnabled(renderer->document()) ? renderer->offsetTop() : LayoutUnit(roundToInt(renderer->offsetTop())); >- double zoomFactor = 1; >- double offsetTopAdjustedWithZoom = adjustForLocalZoom(offsetTop, *renderer, zoomFactor); >- return convertToNonSubpixelValueIfNeeded(offsetTopAdjustedWithZoom, renderer->document(), zoomFactor == 1 ? Floor : Round); >- } >+ if (RenderBoxModelObject* renderer = renderBoxModelObject()) >+ return adjustOffsetForZoomAndSubpixelLayout(renderer, renderer->offsetTop()); > return 0; > } > >@@ -927,12 +967,14 @@ > return 0; > } > >-Element* Element::bindingsOffsetParent() >+Element* Element::offsetParentForBindings() > { > Element* element = offsetParent(); > if (!element || !element->isInShadowTree()) > return element; >- return element->containingShadowRoot()->mode() == ShadowRootMode::UserAgent ? nullptr : element; >+ while (element && !isDescendantOrShadowDescendantOf(&element->rootNode())) >+ element = element->offsetParent(); >+ return element; > } > > Element* Element::offsetParent() >Index: Source/WebCore/dom/Element.h >=================================================================== >--- Source/WebCore/dom/Element.h (revision 239095) >+++ Source/WebCore/dom/Element.h (working copy) >@@ -156,7 +156,9 @@ > WEBCORE_EXPORT void scrollByLines(int lines); > WEBCORE_EXPORT void scrollByPages(int pages); > >+ WEBCORE_EXPORT double offsetLeftForBindings(); > WEBCORE_EXPORT double offsetLeft(); >+ WEBCORE_EXPORT double offsetTopForBindings(); > WEBCORE_EXPORT double offsetTop(); > WEBCORE_EXPORT double offsetWidth(); > WEBCORE_EXPORT double offsetHeight(); >@@ -165,7 +167,7 @@ > > // FIXME: Replace uses of offsetParent in the platform with calls > // to the render layer and merge bindingsOffsetParent and offsetParent. >- WEBCORE_EXPORT Element* bindingsOffsetParent(); >+ WEBCORE_EXPORT Element* offsetParentForBindings(); > > const Element* rootElement() const; > >Index: Source/WebCore/dom/TreeScope.cpp >=================================================================== >--- Source/WebCore/dom/TreeScope.cpp (revision 239095) >+++ Source/WebCore/dom/TreeScope.cpp (working copy) >@@ -179,7 +179,6 @@ > m_elementsByName->remove(name, element); > } > >- > Node& TreeScope::retargetToScope(Node& node) const > { > auto& scope = node.treeScope(); >@@ -211,6 +210,18 @@ > return *shadowRootInLowestCommonTreeScope.host(); > } > >+Element& TreeScope::retargetElementToScope(Element& element) const >+{ >+ auto& scope = element.treeScope(); >+ if (LIKELY(this == &scope || !element.isInShadowTree())) >+ return element; >+ ASSERT(is<ShadowRoot>(scope.rootNode())); >+ >+ auto& node = retargetToScope(element); >+ ASSERT(&node == &element || node.shadowRoot()); >+ return downcast<Element>(node); >+} >+ > Node* TreeScope::ancestorNodeInThisScope(Node* node) const > { > for (; node; node = node->shadowHost()) { >Index: Source/WebCore/dom/TreeScope.h >=================================================================== >--- Source/WebCore/dom/TreeScope.h (revision 239095) >+++ Source/WebCore/dom/TreeScope.h (working copy) >@@ -75,6 +75,7 @@ > > // https://dom.spec.whatwg.org/#retarget > Node& retargetToScope(Node&) const; >+ Element& retargetElementToScope(Element& element) const; > > WEBCORE_EXPORT Node* ancestorNodeInThisScope(Node*) const; > WEBCORE_EXPORT Element* ancestorElementInThisScope(Element*) const; >Index: Source/WebCore/html/HTMLElement.idl >=================================================================== >--- Source/WebCore/html/HTMLElement.idl (revision 239095) >+++ Source/WebCore/html/HTMLElement.idl (working copy) >@@ -52,9 +52,9 @@ > readonly attribute boolean isContentEditable; > > // Extensions from CSSOM-view specification (https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface). >- [ImplementedAs=bindingsOffsetParent] readonly attribute Element? offsetParent; >- readonly attribute double offsetTop; // FIXME: Should be of type long. >- readonly attribute double offsetLeft; // FIXME: Should be of type long. >+ [ImplementedAs=offsetParentForBindings] readonly attribute Element? offsetParent; >+ [ImplementedAs=offsetTopForBindings] readonly attribute double offsetTop; // FIXME: Should be of type long. >+ [ImplementedAs=offsetLeftForBindings] readonly attribute double offsetLeft; // FIXME: Should be of type long. > readonly attribute double offsetWidth; // FIXME: Should be of type long. > readonly attribute double offsetHeight; // FIXME: Should be of type long. > >Index: Source/WebKitLegacy/mac/DOM/DOMElement.mm >=================================================================== >--- Source/WebKitLegacy/mac/DOM/DOMElement.mm (revision 239095) >+++ Source/WebKitLegacy/mac/DOM/DOMElement.mm (working copy) >@@ -81,13 +81,13 @@ > - (int)offsetLeft > { > WebCore::JSMainThreadNullState state; >- return unwrap(*self).offsetLeft(); >+ return unwrap(*self).offsetLeftForBindings(); > } > > - (int)offsetTop > { > WebCore::JSMainThreadNullState state; >- return unwrap(*self).offsetTop(); >+ return unwrap(*self).offsetTopForBindings(); > } > > - (int)offsetWidth >@@ -165,7 +165,7 @@ > - (DOMElement *)offsetParent > { > WebCore::JSMainThreadNullState state; >- return kit(unwrap(*self).bindingsOffsetParent()); >+ return kit(unwrap(*self).offsetParentForBindings()); > } > > - (NSString *)innerHTML
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 157437
:
357294
|
357478
|
357503