WebKit Bugzilla
Attachment 349728 Details for
Bug 174288
: Capturing event listeners are called during bubbling phase for shadow hosts
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch for landing
bug-174288-20180913193915.patch (text/plain), 38.17 KB, created by
Ryosuke Niwa
on 2018-09-13 19:39:16 PDT
(
hide
)
Description:
Patch for landing
Filename:
MIME Type:
Creator:
Ryosuke Niwa
Created:
2018-09-13 19:39:16 PDT
Size:
38.17 KB
patch
obsolete
>Index: Source/WebCore/ChangeLog >=================================================================== >--- Source/WebCore/ChangeLog (revision 235968) >+++ Source/WebCore/ChangeLog (working copy) >@@ -1,3 +1,87 @@ >+2018-09-13 Ryosuke Niwa <rniwa@webkit.org> >+ >+ Capturing event listeners are called during bubbling phase for shadow hosts >+ https://bugs.webkit.org/show_bug.cgi?id=174288 >+ <rdar://problem/33530455> >+ >+ Reviewed by Darin Adler. >+ >+ Implemented the new behavior proposed in https://github.com/whatwg/dom/pull/686 [1] to fix the problem >+ that capturing event listeners on a shadow host is invoked during bubbling phase when an event is >+ dispatched within its shadow tree. >+ >+ To see why this is a problem, suppose we fire a composed event at span#target in the following DOM tree: >+ section#hostParent >+ + div#host -- ShadowRoot >+ - p#parent >+ - span#target >+ Then capturing and bubbling event listeners on #target, #parent, #host, and #hostParent are invoked in >+ the following order in WebKit & Chrome right now: >+ >+ 1. #hostParent, capturing, eventPhase: CAPTURING_PHASE >+ 2. #parent, capturing, eventPhase: CAPTURING_PHASE >+ 3. #target, capturing, eventPhase: AT_TARGET >+ 4. #target, non-capturing, eventPhase: AT_TARGET >+ 5. #parent, non-capturing, eventPhase: BUBBLING_PHASE >+ 6. #host, capturing, eventPhase: AT_TARGET >+ 7. #host, non-capturing, eventPhase: AT_TARGET >+ 8. #hostParent, non-capturing, eventPhase: BUBBLING_PHASE >+ >+ This is counter-intuitive because capturing event listeners on #host isn't invoked until bubblign phase >+ started. A more natural ordering would be: >+ >+ 1. #hostParent, capturing, eventPhase: CAPTURING_PHASE >+ 2. #host, capturing, eventPhase: AT_TARGET >+ 3. #parent, capturing, eventPhase: CAPTURING_PHASE >+ 4. #target, capturing, eventPhase: AT_TARGET >+ 5. #target, non-capturing, eventPhase: AT_TARGET >+ 6. #parent, non-capturing, eventPhase: BUBBLING_PHASE >+ 7. #host, non-capturing, eventPhase: AT_TARGET >+ 8. #hostParent, non-capturing, eventPhase: BUBBLING_PHASE >+ >+ This also happens to be the order by which Gecko's current shadow DOM implementation invoke event listners. >+ This patch implements this new behavior using the spec-change proposed in [1]. Note that this patch also >+ impacts the invocation order of event listeners when there is no shadow tree. Namely, before this patch, >+ event listeners on the event's target is invoked in the registration order. After this patch, all capturing >+ event listeners are invoked before bubbling event listeners are invoked. >+ >+ To implement this behavior, this patch introduces EventTarget::EventInvokePhase indicating whether we're >+ in the capturing phase or bubbling phase to EventTarget::fireEventListeners. We can't use Event's eventPhase >+ enum because that's set to Event::Phase::AT_TARGET when we're at a shadow host. >+ >+ Test: fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html >+ >+ * Modules/modern-media-controls/media/media-controller-support.js: >+ (MediaControllerSupport.prototype.enable): Use capturing event listeners so that we can update the states of >+ media controls before author scripts recieve the event. >+ (MediaControllerSupport.prototype.disable): Ditto. >+ * dom/EventContext.cpp: >+ (WebCore::EventContext::handleLocalEvents const): >+ (WebCore::MouseOrFocusEventContext::handleLocalEvents const): >+ (WebCore::TouchEventContext::handleLocalEvents const): >+ * dom/EventContext.h: >+ * dom/EventDispatcher.cpp: >+ (WebCore::dispatchEventInDOM): Invoke capturing event listners even when target and current target are same. >+ This happens when the current target is a shadow host and event's target is in its shadow tree. Also merged >+ the special code path for the event's target with the code in the bubbling phase. >+ * dom/EventPath.cpp: >+ (WebCore::WindowEventContext::handleLocalEvents const): >+ * dom/EventTarget.cpp: >+ (WebCore::EventTarget::dispatchEvent): Invoke capturing and bubbling event listeners in the order. >+ (WebCore::EventTarget::fireEventListeners): >+ (WebCore::EventTarget::innerInvokeEventListeners): Renamed from fireEventListeners to match the spec. Use >+ EventInvokePhase to filter out event listeners so that we can invoke capturing event listners before bubbling >+ event listeners even when eventPhase is Event::Phase::AT_TARGET. >+ * dom/EventTarget.h: >+ * dom/Node.cpp: >+ (WebCore::Node::handleLocalEvents): >+ * dom/Node.h: >+ * html/HTMLFormElement.cpp: >+ (WebCore::HTMLFormElement::handleLocalEvents): >+ * html/HTMLFormElement.h: >+ * page/DOMWindow.cpp: >+ (WebCore::DOMWindow::dispatchEvent): >+ > 2018-09-11 Ryosuke Niwa <rniwa@webkit.org> > > imported/w3c/web-platform-tests/shadow-dom/form-control-form-attribute.html hits assertion >Index: Source/WebCore/Modules/modern-media-controls/media/media-controller-support.js >=================================================================== >--- Source/WebCore/Modules/modern-media-controls/media/media-controller-support.js (revision 235963) >+++ Source/WebCore/Modules/modern-media-controls/media/media-controller-support.js (working copy) >@@ -38,7 +38,7 @@ class MediaControllerSupport > enable() > { > for (let eventType of this.mediaEvents) >- this.mediaController.media.addEventListener(eventType, this); >+ this.mediaController.media.addEventListener(eventType, this, true); > > for (let tracks of this.tracksToMonitor) { > for (let eventType of ["change", "addtrack", "removetrack"]) >@@ -55,7 +55,7 @@ class MediaControllerSupport > disable() > { > for (let eventType of this.mediaEvents) >- this.mediaController.media.removeEventListener(eventType, this); >+ this.mediaController.media.removeEventListener(eventType, this, true); > > for (let tracks of this.tracksToMonitor) { > for (let eventType of ["change", "addtrack", "removetrack"]) >Index: Source/WebCore/dom/EventContext.cpp >=================================================================== >--- Source/WebCore/dom/EventContext.cpp (revision 235963) >+++ Source/WebCore/dom/EventContext.cpp (working copy) >@@ -45,15 +45,15 @@ EventContext::EventContext(Node* node, E > > EventContext::~EventContext() = default; > >-void EventContext::handleLocalEvents(Event& event) const >+void EventContext::handleLocalEvents(Event& event, EventInvokePhase phase) const > { > event.setTarget(m_target.get()); > event.setCurrentTarget(m_currentTarget.get()); > // FIXME: Consider merging handleLocalEvents and fireEventListeners. > if (m_node) >- m_node->handleLocalEvents(event); >+ m_node->handleLocalEvents(event, phase); > else >- m_currentTarget->fireEventListeners(event); >+ m_currentTarget->fireEventListeners(event, phase); > } > > bool EventContext::isMouseOrFocusEventContext() const >@@ -73,11 +73,11 @@ MouseOrFocusEventContext::MouseOrFocusEv > > MouseOrFocusEventContext::~MouseOrFocusEventContext() = default; > >-void MouseOrFocusEventContext::handleLocalEvents(Event& event) const >+void MouseOrFocusEventContext::handleLocalEvents(Event& event, EventInvokePhase phase) const > { > if (m_relatedTarget) > event.setRelatedTarget(*m_relatedTarget); >- EventContext::handleLocalEvents(event); >+ EventContext::handleLocalEvents(event, phase); > } > > bool MouseOrFocusEventContext::isMouseOrFocusEventContext() const >@@ -97,7 +97,7 @@ TouchEventContext::TouchEventContext(Nod > > TouchEventContext::~TouchEventContext() = default; > >-void TouchEventContext::handleLocalEvents(Event& event) const >+void TouchEventContext::handleLocalEvents(Event& event, EventInvokePhase phase) const > { > checkReachability(m_touches); > checkReachability(m_targetTouches); >@@ -106,7 +106,7 @@ void TouchEventContext::handleLocalEvent > touchEvent.setTouches(m_touches.ptr()); > touchEvent.setTargetTouches(m_targetTouches.ptr()); > touchEvent.setChangedTouches(m_changedTouches.ptr()); >- EventContext::handleLocalEvents(event); >+ EventContext::handleLocalEvents(event, phase); > } > > bool TouchEventContext::isTouchEventContext() const >Index: Source/WebCore/dom/EventContext.h >=================================================================== >--- Source/WebCore/dom/EventContext.h (revision 235963) >+++ Source/WebCore/dom/EventContext.h (working copy) >@@ -36,6 +36,8 @@ class TouchList; > class EventContext { > WTF_MAKE_FAST_ALLOCATED; > public: >+ using EventInvokePhase = EventTarget::EventInvokePhase; >+ > EventContext(Node*, EventTarget* currentTarget, EventTarget*); > virtual ~EventContext(); > >@@ -43,7 +45,7 @@ public: > EventTarget* currentTarget() const { return m_currentTarget.get(); } > EventTarget* target() const { return m_target.get(); } > >- virtual void handleLocalEvents(Event&) const; >+ virtual void handleLocalEvents(Event&, EventInvokePhase) const; > > virtual bool isMouseOrFocusEventContext() const; > virtual bool isTouchEventContext() const; >@@ -67,7 +69,7 @@ public: > void setRelatedTarget(Node*); > > private: >- void handleLocalEvents(Event&) const final; >+ void handleLocalEvents(Event&, EventInvokePhase) const final; > bool isMouseOrFocusEventContext() const final; > > RefPtr<Node> m_relatedTarget; >@@ -84,7 +86,7 @@ public: > TouchList& touchList(TouchListType); > > private: >- void handleLocalEvents(Event&) const final; >+ void handleLocalEvents(Event&, EventInvokePhase) const final; > bool isTouchEventContext() const final; > > void checkReachability(const Ref<TouchList>&) const; >Index: Source/WebCore/dom/EventDispatcher.cpp >=================================================================== >--- Source/WebCore/dom/EventDispatcher.cpp (revision 235963) >+++ Source/WebCore/dom/EventDispatcher.cpp (working copy) >@@ -75,26 +75,21 @@ static void callDefaultEventHandlersInBu > > static void dispatchEventInDOM(Event& event, const EventPath& path) > { >- // Trigger capturing event handlers, starting at the top and working our way down. >- event.setEventPhase(Event::CAPTURING_PHASE); >- >- for (size_t i = path.size() - 1; i > 0; --i) { >- const EventContext& eventContext = path.contextAt(i); >+ // Invoke capturing event listeners in the reverse order. >+ for (size_t i = path.size(); i > 0; --i) { >+ const EventContext& eventContext = path.contextAt(i - 1); > if (eventContext.currentTarget() == eventContext.target()) >- continue; >- eventContext.handleLocalEvents(event); >+ event.setEventPhase(Event::AT_TARGET); >+ else >+ event.setEventPhase(Event::CAPTURING_PHASE); >+ eventContext.handleLocalEvents(event, EventTarget::EventInvokePhase::Capturing); > if (event.propagationStopped()) > return; > } > >- event.setEventPhase(Event::AT_TARGET); >- path.contextAt(0).handleLocalEvents(event); >- if (event.propagationStopped()) >- return; >- >- // Trigger bubbling event handlers, starting at the bottom and working our way up. >+ // Invoke bubbling event listeners. > size_t size = path.size(); >- for (size_t i = 1; i < size; ++i) { >+ for (size_t i = 0; i < size; ++i) { > const EventContext& eventContext = path.contextAt(i); > if (eventContext.currentTarget() == eventContext.target()) > event.setEventPhase(Event::AT_TARGET); >@@ -102,7 +97,7 @@ static void dispatchEventInDOM(Event& ev > event.setEventPhase(Event::BUBBLING_PHASE); > else > continue; >- eventContext.handleLocalEvents(event); >+ eventContext.handleLocalEvents(event, EventTarget::EventInvokePhase::Bubbling); > if (event.propagationStopped()) > return; > } >Index: Source/WebCore/dom/EventPath.cpp >=================================================================== >--- Source/WebCore/dom/EventPath.cpp (revision 235963) >+++ Source/WebCore/dom/EventPath.cpp (working copy) >@@ -37,7 +37,7 @@ class WindowEventContext final : public > public: > WindowEventContext(Node&, DOMWindow&, EventTarget&); > private: >- void handleLocalEvents(Event&) const final; >+ void handleLocalEvents(Event&, EventInvokePhase) const final; > }; > > inline WindowEventContext::WindowEventContext(Node& node, DOMWindow& currentTarget, EventTarget& target) >@@ -45,11 +45,11 @@ inline WindowEventContext::WindowEventCo > { > } > >-void WindowEventContext::handleLocalEvents(Event& event) const >+void WindowEventContext::handleLocalEvents(Event& event, EventInvokePhase phase) const > { > event.setTarget(m_target.get()); > event.setCurrentTarget(m_currentTarget.get()); >- m_currentTarget->fireEventListeners(event); >+ m_currentTarget->fireEventListeners(event, phase); > } > > static inline bool shouldEventCrossShadowBoundary(Event& event, ShadowRoot& shadowRoot, EventTarget& target) >Index: Source/WebCore/dom/EventTarget.cpp >=================================================================== >--- Source/WebCore/dom/EventTarget.cpp (revision 235963) >+++ Source/WebCore/dom/EventTarget.cpp (working copy) >@@ -183,6 +183,7 @@ ExceptionOr<bool> EventTarget::dispatchE > > void EventTarget::dispatchEvent(Event& event) > { >+ // FIXME: We should always use EventDispatcher. > ASSERT(event.isInitialized()); > ASSERT(!event.isBeingDispatched()); > >@@ -190,7 +191,8 @@ void EventTarget::dispatchEvent(Event& e > event.setCurrentTarget(this); > event.setEventPhase(Event::AT_TARGET); > event.resetBeforeDispatch(); >- fireEventListeners(event); >+ fireEventListeners(event, EventInvokePhase::Capturing); >+ fireEventListeners(event, EventInvokePhase::Bubbling); > event.resetAfterDispatch(); > } > >@@ -219,7 +221,8 @@ static const AtomicString& legacyType(co > return nullAtom(); > } > >-void EventTarget::fireEventListeners(Event& event) >+// https://dom.spec.whatwg.org/#concept-event-listener-invoke >+void EventTarget::fireEventListeners(Event& event, EventInvokePhase phase) > { > ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::isEventAllowedInMainThread()); > ASSERT(event.isInitialized()); >@@ -231,7 +234,7 @@ void EventTarget::fireEventListeners(Eve > SetForScope<bool> firingEventListenersScope(data->isFiringEventListeners, true); > > if (auto* listenersVector = data->eventListenerMap.find(event.type())) { >- fireEventListeners(event, *listenersVector); >+ innerInvokeEventListeners(event, *listenersVector, phase); > return; > } > >@@ -244,7 +247,7 @@ void EventTarget::fireEventListeners(Eve > if (auto* legacyListenersVector = data->eventListenerMap.find(legacyTypeName)) { > AtomicString typeName = event.type(); > event.setType(legacyTypeName); >- fireEventListeners(event, *legacyListenersVector); >+ innerInvokeEventListeners(event, *legacyListenersVector, phase); > event.setType(typeName); > } > } >@@ -252,7 +255,8 @@ void EventTarget::fireEventListeners(Eve > > // Intentionally creates a copy of the listeners vector to avoid event listeners added after this point from being run. > // Note that removal still has an effect due to the removed field in RegisteredEventListener. >-void EventTarget::fireEventListeners(Event& event, EventListenerVector listeners) >+// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke >+void EventTarget::innerInvokeEventListeners(Event& event, EventListenerVector listeners, EventInvokePhase phase) > { > Ref<EventTarget> protectedThis(*this); > ASSERT(!listeners.isEmpty()); >@@ -268,9 +272,9 @@ void EventTarget::fireEventListeners(Eve > if (UNLIKELY(registeredListener->wasRemoved())) > continue; > >- if (event.eventPhase() == Event::CAPTURING_PHASE && !registeredListener->useCapture()) >+ if (phase == EventInvokePhase::Capturing && !registeredListener->useCapture()) > continue; >- if (event.eventPhase() == Event::BUBBLING_PHASE && registeredListener->useCapture()) >+ if (phase == EventInvokePhase::Bubbling && registeredListener->useCapture()) > continue; > > if (InspectorInstrumentation::isEventListenerDisabled(*this, event.type(), registeredListener->callback(), registeredListener->useCapture())) >Index: Source/WebCore/dom/EventTarget.h >=================================================================== >--- Source/WebCore/dom/EventTarget.h (revision 235963) >+++ Source/WebCore/dom/EventTarget.h (working copy) >@@ -102,7 +102,8 @@ public: > bool hasActiveEventListeners(const AtomicString& eventType) const; > const EventListenerVector& eventListeners(const AtomicString& eventType); > >- void fireEventListeners(Event&); >+ enum class EventInvokePhase { Capturing, Bubbling }; >+ void fireEventListeners(Event&, EventInvokePhase); > bool isFiringEventListeners() const; > > void visitJSEventListeners(JSC::SlotVisitor&); >@@ -120,7 +121,7 @@ private: > virtual void refEventTarget() = 0; > virtual void derefEventTarget() = 0; > >- void fireEventListeners(Event&, EventListenerVector); >+ void innerInvokeEventListeners(Event&, EventListenerVector, EventInvokePhase); > > friend class EventListenerIterator; > }; >Index: Source/WebCore/dom/Node.cpp >=================================================================== >--- Source/WebCore/dom/Node.cpp (revision 235963) >+++ Source/WebCore/dom/Node.cpp (working copy) >@@ -2307,7 +2307,7 @@ void Node::notifyMutationObserversNodeWi > } > } > >-void Node::handleLocalEvents(Event& event) >+void Node::handleLocalEvents(Event& event, EventInvokePhase phase) > { > if (!hasEventTargetData()) > return; >@@ -2316,7 +2316,7 @@ void Node::handleLocalEvents(Event& even > if (is<Element>(*this) && downcast<Element>(*this).isDisabledFormControl() && event.isMouseEvent() && !event.isWheelEvent()) > return; > >- fireEventListeners(event); >+ fireEventListeners(event, phase); > } > > void Node::dispatchScopedEvent(Event& event) >Index: Source/WebCore/dom/Node.h >=================================================================== >--- Source/WebCore/dom/Node.h (revision 235963) >+++ Source/WebCore/dom/Node.h (working copy) >@@ -486,7 +486,7 @@ public: > > void dispatchScopedEvent(Event&); > >- virtual void handleLocalEvents(Event&); >+ virtual void handleLocalEvents(Event&, EventInvokePhase); > > void dispatchSubtreeModifiedEvent(); > void dispatchDOMActivateEvent(Event& underlyingClickEvent); >Index: Source/WebCore/html/HTMLFormElement.cpp >=================================================================== >--- Source/WebCore/html/HTMLFormElement.cpp (revision 235963) >+++ Source/WebCore/html/HTMLFormElement.cpp (working copy) >@@ -142,13 +142,13 @@ void HTMLFormElement::removedFromAncesto > HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree); > } > >-void HTMLFormElement::handleLocalEvents(Event& event) >+void HTMLFormElement::handleLocalEvents(Event& event, EventInvokePhase phase) > { > if (event.eventPhase() != Event::CAPTURING_PHASE && is<Node>(event.target()) && event.target() != this && (event.type() == eventNames().submitEvent || event.type() == eventNames().resetEvent)) { > event.stopPropagation(); > return; > } >- HTMLElement::handleLocalEvents(event); >+ HTMLElement::handleLocalEvents(event, phase); > } > > unsigned HTMLFormElement::length() const >Index: Source/WebCore/html/HTMLFormElement.h >=================================================================== >--- Source/WebCore/html/HTMLFormElement.h (revision 235963) >+++ Source/WebCore/html/HTMLFormElement.h (working copy) >@@ -133,7 +133,7 @@ private: > void removedFromAncestor(RemovalType, ContainerNode&) final; > void finishParsingChildren() final; > >- void handleLocalEvents(Event&) final; >+ void handleLocalEvents(Event&, EventInvokePhase) final; > > void parseAttribute(const QualifiedName&, const AtomicString&) final; > bool isURLAttribute(const Attribute&) const final; >Index: Source/WebCore/page/DOMWindow.cpp >=================================================================== >--- Source/WebCore/page/DOMWindow.cpp (revision 235963) >+++ Source/WebCore/page/DOMWindow.cpp (working copy) >@@ -2052,7 +2052,9 @@ void DOMWindow::dispatchEvent(Event& eve > event.setEventPhase(Event::AT_TARGET); > event.resetBeforeDispatch(); > auto cookie = InspectorInstrumentation::willDispatchEventOnWindow(frame(), event, *this); >- fireEventListeners(event); >+ // FIXME: We should use EventDispatcher everywhere. >+ fireEventListeners(event, EventInvokePhase::Capturing); >+ fireEventListeners(event, EventInvokePhase::Bubbling); > InspectorInstrumentation::didDispatchEventOnWindow(cookie); > event.resetAfterDispatch(); > } >Index: LayoutTests/ChangeLog >=================================================================== >--- LayoutTests/ChangeLog (revision 235963) >+++ LayoutTests/ChangeLog (working copy) >@@ -1,3 +1,22 @@ >+2018-09-13 Ryosuke Niwa <rniwa@webkit.org> >+ >+ Capturing event listeners are called during bubbling phase for shadow hosts >+ https://bugs.webkit.org/show_bug.cgi?id=174288 >+ >+ Reviewed by Darin Adler. >+ >+ Added a W3C style testharness.js test and rebaselined two tests. See below for rationals of rebaselines. >+ >+ * fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees-expected.txt: Added. >+ * fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html: Added. >+ >+ * media/media-load-event-expected.txt: Rebaselined. The logging of oncanplaythrough event is now happening >+ before canplaythrough() is called because the logging is done by waitForEvent which uses a capturing event >+ listener whereas canplaythrough is called by a event handler, which is non-capturing. >+ >+ * platform/ios-11/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt: >+ * platform/ios/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt: >+ > 2018-09-12 Dean Jackson <dino@grorg.org> > > Header parsing for experimental and internal debug features >Index: LayoutTests/fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees-expected.txt >=================================================================== >--- LayoutTests/fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees-expected.txt (nonexistent) >+++ LayoutTests/fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees-expected.txt (working copy) >@@ -0,0 +1,7 @@ >+ >+PASS Capturing event listeners should be invoked before bubbling event listeners on the target without shadow trees >+PASS Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a shadow tree >+PASS Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a doubly nested shadow tree >+PASS Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched via a slot >+PASS Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a shadow tree which passes through another shadow tree >+ >Index: LayoutTests/fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html >=================================================================== >--- LayoutTests/fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html (nonexistent) >+++ LayoutTests/fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html (working copy) >@@ -0,0 +1,191 @@ >+<!DOCTYPE html> >+<html> >+<head> >+<title>Shadow DOM: Capturing event listeners should be invoked before bubbling event listeners</title> >+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"> >+<script src="../../resources/testharness.js"></script> >+<script src="../../resources/testharnessreport.js"></script> >+<script src="../../imported/w3c/web-platform-tests/shadow-dom/resources/shadow-dom.js"></script> >+<script> >+ >+function attachEventListeners(eventType, tree) { >+ const eventLogs = []; >+ const makeComposedPathResult = (event) => event.composedPath().map((node) => node.id) >+ for (const id in tree) { >+ const node = tree[id]; >+ node.addEventListener(eventType, event => eventLogs.push( >+ ['bubbling', event.eventPhase, event.target.id, event.currentTarget.id, makeComposedPathResult(event)]), {capture: false}); >+ node.addEventListener(eventType, event => eventLogs.push( >+ ['capturing', event.eventPhase, event.target.id, event.currentTarget.id, makeComposedPathResult(event)]), {capture: true}); >+ } >+ return eventLogs; >+} >+ >+</script> >+</head> >+<body> >+ >+<div id="test1"> >+ <div id="parent"> >+ <div id="target"></div> >+ </div> >+</div> >+<script> >+test(() => { >+ const tree = createTestTree(document.getElementById('test1')); >+ const logs = attachEventListeners('my-event', tree); >+ tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true })); >+ >+ const composedPath = ['target', 'parent', 'test1']; >+ assert_object_equals(logs, [ >+ ['capturing', Event.CAPTURING_PHASE, 'target', 'test1', composedPath], >+ ['capturing', Event.CAPTURING_PHASE, 'target', 'parent', composedPath], >+ ['capturing', Event.AT_TARGET, 'target', 'target', composedPath], >+ ['bubbling', Event.AT_TARGET, 'target', 'target', composedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'target', 'parent', composedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'target', 'test1', composedPath], >+ ]); >+}, 'Capturing event listeners should be invoked before bubbling event listeners on the target without shadow trees'); >+</script> >+ >+<div id="test2"> >+ <div id="host"> >+ <template id="shadowRoot" data-mode="closed"> >+ <div id="target"></div> >+ </template> >+ </div> >+</div> >+<script> >+test(() => { >+ const tree = createTestTree(document.getElementById('test2')); >+ const logs = attachEventListeners('my-event', tree); >+ tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true })); >+ >+ const innerComposedPath = ['target', 'shadowRoot', 'host', 'test2']; >+ const outerComposedPath = ['host', 'test2']; >+ assert_object_equals(logs, [ >+ ['capturing', Event.CAPTURING_PHASE, 'host', 'test2', outerComposedPath], >+ ['capturing', Event.AT_TARGET, 'host', 'host', outerComposedPath], >+ ['capturing', Event.CAPTURING_PHASE, 'target', 'shadowRoot', innerComposedPath], >+ ['capturing', Event.AT_TARGET, 'target', 'target', innerComposedPath], >+ ['bubbling', Event.AT_TARGET, 'target', 'target', innerComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'target', 'shadowRoot', innerComposedPath], >+ ['bubbling', Event.AT_TARGET, 'host', 'host', outerComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'host', 'test2', outerComposedPath], >+ ]); >+}, 'Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a shadow tree'); >+</script> >+ >+<div id="test3"> >+ <div id="outerHost"> >+ <template id="outerShadowRoot" data-mode="closed"> >+ <div id="innerHost"> >+ <template id="innerShadowRoot" data-mode="closed"> >+ <div id="target"></div> >+ </template> >+ </div> >+ </template> >+ </div> >+</div> >+<script> >+test(() => { >+ const tree = createTestTree(document.getElementById('test3')); >+ const logs = attachEventListeners('my-event', tree); >+ tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true })); >+ >+ const innerShadowComposedPath = ['target', 'innerShadowRoot', 'innerHost', 'outerShadowRoot', 'outerHost', 'test3']; >+ const outerShadowComposedPath = ['innerHost', 'outerShadowRoot', 'outerHost', 'test3']; >+ const outerComposedPath = ['outerHost', 'test3']; >+ assert_object_equals(logs, [ >+ ['capturing', Event.CAPTURING_PHASE, 'outerHost', 'test3', outerComposedPath], >+ ['capturing', Event.AT_TARGET, 'outerHost', 'outerHost', outerComposedPath], >+ ['capturing', Event.CAPTURING_PHASE, 'innerHost', 'outerShadowRoot', outerShadowComposedPath], >+ ['capturing', Event.AT_TARGET, 'innerHost', 'innerHost', outerShadowComposedPath], >+ ['capturing', Event.CAPTURING_PHASE, 'target', 'innerShadowRoot', innerShadowComposedPath], >+ ['capturing', Event.AT_TARGET, 'target', 'target', innerShadowComposedPath], >+ >+ ['bubbling', Event.AT_TARGET, 'target', 'target', innerShadowComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'target', 'innerShadowRoot', innerShadowComposedPath], >+ ['bubbling', Event.AT_TARGET, 'innerHost', 'innerHost', outerShadowComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'innerHost', 'outerShadowRoot', outerShadowComposedPath], >+ ['bubbling', Event.AT_TARGET, 'outerHost', 'outerHost', outerComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'outerHost', 'test3', outerComposedPath], >+ ]); >+}, 'Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a doubly nested shadow tree'); >+</script> >+ >+<div id="test4"> >+ <div id="host"> >+ <template id="shadowRoot" data-mode="closed"> >+ <slot id="slot"></slot> >+ </template> >+ <div id="target"></div> >+ </div> >+</div> >+<script> >+test(() => { >+ const tree = createTestTree(document.getElementById('test4')); >+ const logs = attachEventListeners('my-event', tree); >+ tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true })); >+ >+ const innerComposedPath = ['target', 'slot', 'shadowRoot', 'host', 'test4']; >+ const outerComposedPath = ['target', 'host', 'test4']; >+ assert_object_equals(logs, [ >+ ['capturing', Event.CAPTURING_PHASE, 'target', 'test4', outerComposedPath], >+ ['capturing', Event.CAPTURING_PHASE, 'target', 'host', outerComposedPath], >+ ['capturing', Event.CAPTURING_PHASE, 'target', 'shadowRoot', innerComposedPath], >+ ['capturing', Event.CAPTURING_PHASE, 'target', 'slot', innerComposedPath], >+ ['capturing', Event.AT_TARGET, 'target', 'target', outerComposedPath], >+ >+ ['bubbling', Event.AT_TARGET, 'target', 'target', outerComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'target', 'slot', innerComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'target', 'shadowRoot', innerComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'target', 'host', outerComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'target', 'test4', outerComposedPath], >+ ]); >+}, 'Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched via a slot'); >+</script> >+ >+<div id="test5"> >+ <div id="upperHost"> >+ <template id="upperShadowRoot" data-mode="closed"> >+ <slot id="upperSlot"></slot> >+ </template> >+ <div id="lowerHost"> >+ <template id="lowerShadowRoot" data-mode="closed"> >+ <div id="target"></div> >+ </template> >+ </div> >+ </div> >+</div> >+<script> >+test(() => { >+ const tree = createTestTree(document.getElementById('test5')); >+ const logs = attachEventListeners('my-event', tree); >+ tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true })); >+ >+ const lowerComposedPath = ['target', 'lowerShadowRoot', 'lowerHost', 'upperHost', 'test5']; >+ const upperComposedPath = ['lowerHost', 'upperSlot', 'upperShadowRoot', 'upperHost', 'test5']; >+ const outerComposedPath = ['lowerHost', 'upperHost', 'test5']; >+ assert_object_equals(logs, [ >+ ['capturing', Event.CAPTURING_PHASE, 'lowerHost', 'test5', outerComposedPath], >+ ['capturing', Event.CAPTURING_PHASE, 'lowerHost', 'upperHost', outerComposedPath], >+ ['capturing', Event.CAPTURING_PHASE, 'lowerHost', 'upperShadowRoot', upperComposedPath], >+ ['capturing', Event.CAPTURING_PHASE, 'lowerHost', 'upperSlot', upperComposedPath], >+ ['capturing', Event.AT_TARGET, 'lowerHost', 'lowerHost', outerComposedPath], >+ ['capturing', Event.CAPTURING_PHASE, 'target', 'lowerShadowRoot', lowerComposedPath], >+ ['capturing', Event.AT_TARGET, 'target', 'target', lowerComposedPath], >+ >+ ['bubbling', Event.AT_TARGET, 'target', 'target', lowerComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'target', 'lowerShadowRoot', lowerComposedPath], >+ ['bubbling', Event.AT_TARGET, 'lowerHost', 'lowerHost', outerComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'lowerHost', 'upperSlot', upperComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'lowerHost', 'upperShadowRoot', upperComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'lowerHost', 'upperHost', outerComposedPath], >+ ['bubbling', Event.BUBBLING_PHASE, 'lowerHost', 'test5', outerComposedPath], >+ ]); >+}, 'Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a shadow tree which passes through another shadow tree'); >+</script> >+ >+</body> >+</html> >Index: LayoutTests/imported/w3c/ChangeLog >=================================================================== >--- LayoutTests/imported/w3c/ChangeLog (revision 235999) >+++ LayoutTests/imported/w3c/ChangeLog (working copy) >@@ -1,3 +1,20 @@ >+2018-09-12 Ryosuke Niwa <rniwa@webkit.org> >+ >+ Capturing event listeners are called during bubbling phase for shadow hosts >+ https://bugs.webkit.org/show_bug.cgi?id=174288 >+ >+ Reviewed by Darin Adler. >+ >+ * web-platform-tests/dom/events/Event-dispatch-handlers-changed-expected.txt: Rebaselined. This test's >+ expectation is now wrong because event listner 3 is added after the event listener list is cloned for >+ capturing event listeners but before cloned for bubbling event listeners. As a result, event listener 3 >+ is now invoked. It used to be not called because both bubbling and capturing event listeners are called >+ after cloning the event listner list once, which didn't have event listener 3. >+ >+ * web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt: Rebaselined. This test expects >+ event listener 2, which is bubbling, to be called between two capturing event listeners 1 and 3, which >+ is no longer true after this patch. >+ > 2018-09-12 Chris Dumez <cdumez@apple.com> > > Unreviewed, rebaseline imported/w3c/web-platform-tests/url/failure.html after r235808. >Index: LayoutTests/imported/w3c/web-platform-tests/dom/events/Event-dispatch-handlers-changed-expected.txt >=================================================================== >--- LayoutTests/imported/w3c/web-platform-tests/dom/events/Event-dispatch-handlers-changed-expected.txt (revision 235963) >+++ LayoutTests/imported/w3c/web-platform-tests/dom/events/Event-dispatch-handlers-changed-expected.txt (working copy) >@@ -1,3 +1,3 @@ > >-PASS Dispatch additional events inside an event listener >+FAIL Dispatch additional events inside an event listener assert_array_equals: actual_targets lengths differ, expected 16 got 17 > >Index: LayoutTests/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt >=================================================================== >--- LayoutTests/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt (revision 235963) >+++ LayoutTests/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt (working copy) >@@ -25,5 +25,5 @@ PASS If the event's initialized flag is > PASS If the event's dispatch flag is set, an InvalidStateError must be thrown. > PASS Exceptions from event listeners must not be propagated. > PASS Event listeners added during dispatch should be called >-PASS Event listeners should be called in order of addition >+FAIL Event listeners should be called in order of addition assert_array_equals: property 1, expected 2 but got 3 > >Index: LayoutTests/media/media-load-event-expected.txt >=================================================================== >--- LayoutTests/media/media-load-event-expected.txt (revision 235963) >+++ LayoutTests/media/media-load-event-expected.txt (working copy) >@@ -8,11 +8,11 @@ EVENT(loadstart) > EVENT(durationchange) > EVENT(loadeddata) > EVENT(canplaythrough) >+EVENT(canplaythrough) > > RUN(document.getElementById('parent').appendChild(mediaElement)) > RUN(mediaElement.play()) > >-EVENT(canplaythrough) > EVENT(play) > EVENT(playing) > >Index: LayoutTests/platform/ios-11/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt >=================================================================== >--- LayoutTests/platform/ios-11/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt (revision 235963) >+++ LayoutTests/platform/ios-11/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt (working copy) >@@ -25,5 +25,5 @@ PASS If the event's initialized flag is > PASS If the event's dispatch flag is set, an InvalidStateError must be thrown. > PASS Exceptions from event listeners must not be propagated. > PASS Event listeners added during dispatch should be called >-PASS Event listeners should be called in order of addition >+FAIL Event listeners should be called in order of addition assert_array_equals: property 1, expected 2 but got 3 > >Index: LayoutTests/platform/ios/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt >=================================================================== >--- LayoutTests/platform/ios/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt (revision 235963) >+++ LayoutTests/platform/ios/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt (working copy) >@@ -25,5 +25,5 @@ PASS If the event's initialized flag is > PASS If the event's dispatch flag is set, an InvalidStateError must be thrown. > PASS Exceptions from event listeners must not be propagated. > PASS Event listeners added during dispatch should be called >-PASS Event listeners should be called in order of addition >+PASS Event listeners should be +FAIL Event listeners should be called in order of addition assert_array_equals: property 1, expected 2 but got 3 >
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 174288
:
349618
|
349621
|
349631
|
349640
|
349642
|
349654
|
349713
|
349718
| 349728