- Revision
- 208641
- Author
- [email protected]
- Date
- 2016-11-11 21:03:27 -0800 (Fri, 11 Nov 2016)
Log Message
event.composedPath() does not include window
https://bugs.webkit.org/show_bug.cgi?id=164609
<rdar://problem/29210383>
Reviewed by Antti Koivisto.
Source/WebCore:
Fixed the bug by including WindowContext be a part of the regular EventPath. This also simplifies
dispatchEventInDOM which used to had a special logic for dispatching an event on the window.
Also fixed a bug in EventDispatcher::dispatchEvent that event.target would be nullptr when an event was
dispatched inside a disconnected shadow tree or prevented from propagating to the document tree.
Preserve the final target by simply saving event.target() prior to invoking the default event handler instead.
Test: fast/shadow-dom/event-path-with-window.html
* dom/EventDispatcher.cpp:
(WebCore::WindowEventContext): Deleted. Moved to EventPath.cpp.
(WebCore::dispatchEventInDOM): Removed the code for WindowContext. The generic event dispatching logic
will do the same work now.
(WebCore::EventDispatcher::dispatchEvent): Restore the original target instead of using that of WindowContext.
* dom/EventPath.cpp:
(WebCore::WindowEventContext): Moved from EventDispatcher.cpp. Also made it a subclass of EventContext.
(WebCore::WindowEventContext::handleLocalEvents): Added.
(WebCore::EventPath::EventPath): When the parent's nullptr, check if the current node is Document. If it is,
follow https://dom.spec.whatwg.org/#interface-document where it says:
"A document’s get the parent algorithm, given an event, returns null if event’s type attribute value is 'load'
or document does not have a browsing context, and the document’s associated Window object otherwise."
(WebCore::EventPath::setRelatedTarget): Skip over WindowContext.
(WebCore::EventPath::retargetTouch): Ditto.
(WebCore::EventPath::computePathUnclosedToTarget): When the target is DOMWindow, use its document as the target.
Also, include any event target that is not a node in the event path.
LayoutTests:
Added a W3C style testharness.js test for dispatching an inside a shadow tree connected to a document.
* fast/shadow-dom/event-path-with-window-expected.txt: Added.
* fast/shadow-dom/event-path-with-window.html: Added.
* fast/shadow-dom/resources/event-path-test-helpers.js:
(dispatchEventWithLog): Traverse from document to window. Also include the event object in the log.
Modified Paths
Added Paths
Diff
Modified: trunk/LayoutTests/ChangeLog (208640 => 208641)
--- trunk/LayoutTests/ChangeLog 2016-11-12 04:00:55 UTC (rev 208640)
+++ trunk/LayoutTests/ChangeLog 2016-11-12 05:03:27 UTC (rev 208641)
@@ -1,3 +1,18 @@
+2016-11-11 Ryosuke Niwa <[email protected]>
+
+ event.composedPath() does not include window
+ https://bugs.webkit.org/show_bug.cgi?id=164609
+ <rdar://problem/29210383>
+
+ Reviewed by Antti Koivisto.
+
+ Added a W3C style testharness.js test for dispatching an inside a shadow tree connected to a document.
+
+ * fast/shadow-dom/event-path-with-window-expected.txt: Added.
+ * fast/shadow-dom/event-path-with-window.html: Added.
+ * fast/shadow-dom/resources/event-path-test-helpers.js:
+ (dispatchEventWithLog): Traverse from document to window. Also include the event object in the log.
+
2016-11-11 Joseph Pecoraro <[email protected]>
test262: DataView get methods should allow for missing offset, set methods should allow for missing value
Added: trunk/LayoutTests/fast/shadow-dom/event-path-with-window-expected.txt (0 => 208641)
--- trunk/LayoutTests/fast/shadow-dom/event-path-with-window-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/event-path-with-window-expected.txt 2016-11-12 05:03:27 UTC (rev 208641)
@@ -0,0 +1,8 @@
+
+PASS The event must propagate out of open mode shadow boundaries when the composed flag is set
+PASS The event must propagate out of closed mode shadow boundaries when the composed flag is set
+PASS The event must propagate out of open mode shadow boundaries when the composed flag is set
+PASS The event must propagate out of closed mode shadow boundaries when the composed flag is set
+PASS The event must not propagate out of open mode shadow tree in which the relative target and the relative related target are the same
+PASS The event must not propagate out of closed mode shadow tree in which the relative target and the relative related target are the same
+
Added: trunk/LayoutTests/fast/shadow-dom/event-path-with-window.html (0 => 208641)
--- trunk/LayoutTests/fast/shadow-dom/event-path-with-window.html (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/event-path-with-window.html 2016-11-12 05:03:27 UTC (rev 208641)
@@ -0,0 +1,134 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Shadow DOM: Extensions to Event Interface</title>
+<meta name="author" title="Ryosuke Niwa" href=""
+<meta name="assert" content="Event interface must have composedPath() as a method">
+<link rel="help" href=""
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+window.label = 'window';
+document.label = 'document';
+document.documentElement.label = 'html';
+document.body.label = 'body';
+
+// FIXME: Add a test for trimming event path.
+
+/*
+-SR: ShadowRoot -S: Slot target: (~) *: indicates start digit: event path order
+A ------------------------------- A-SR
++ B ------------ B-SR + A1 ------ A1-SR (1)
+ + C + B1 --- B1-SR + A2-S + A1a (*; 0)
+ + D --- D-SR + B1a + B1b --- B1b-SR
+ + D1 + B1c-S + B1b1
+ + B1b2
+*/
+
+function testEventTargetAfterDispatching(mode) {
+ test(() => {
+ const nodes = createTestTree(mode);
+ const log = dispatchEventWithLog(nodes, nodes.A1a, new Event('my-event', {composed: false, bubbles: true}));
+
+ const expectedPath = ['A1a', 'A1-SR'];
+ assert_array_equals(log.eventPath, expectedPath);
+ assert_array_equals(log.eventPath.length, log.pathAtTargets.length);
+ assert_array_equals(log.pathAtTargets[0], expectedPath);
+ assert_array_equals(log.pathAtTargets[1], expectedPath);
+
+ assert_equals(log.event.target, nodes.A1a);
+ assert_equals(log.event.currentTarget, null);
+ }, 'The event must propagate out of ' + mode + ' mode shadow boundaries when the composed flag is set');
+}
+
+testEventTargetAfterDispatching('open');
+testEventTargetAfterDispatching('closed');
+
+/*
+-SR: ShadowRoot -S: Slot target: (~) *: indicates start digit: event path order
+window (7)
+ + document (6)
+ + body (5)
+ + A (4) --------------------------- A-SR (3)
+ + B ------------ B-SR + A1 (2) --- A1-SR (1)
+ + C + B1 --- B1-SR + A2-S + A1a (*; 0)
+ + D --- D-SR + B1a + B1b --- B1b-SR
+ + D1 + B1c-S + B1b1
+ + B1b2
+*/
+
+function testComposedEventInDocument(mode) {
+ test(() => {
+ const nodes = createTestTree(mode);
+ document.body.appendChild(nodes.A);
+
+ const log = dispatchEventWithLog(nodes, nodes.A1a, new Event('my-event', {composed: true, bubbles: true}));
+ let expectedPath = ['A1a', 'A1-SR', 'A1', 'A-SR', 'A', 'body', 'html', 'document', 'window'];
+
+ assert_array_equals(log.eventPath, expectedPath);
+ assert_array_equals(log.eventPath.length, log.pathAtTargets.length);
+ assert_array_equals(log.pathAtTargets[0], expectedPath);
+ assert_array_equals(log.pathAtTargets[1], expectedPath);
+ if (mode != 'open')
+ expectedPath = expectedPath.slice(expectedPath.indexOf('A1'));
+ assert_array_equals(log.pathAtTargets[2], expectedPath);
+ assert_array_equals(log.pathAtTargets[3], expectedPath);
+ if (mode != 'open')
+ expectedPath = expectedPath.slice(expectedPath.indexOf('A'));
+ assert_array_equals(log.pathAtTargets[4], expectedPath);
+ assert_array_equals(log.pathAtTargets[5], expectedPath);
+ assert_array_equals(log.pathAtTargets[6], expectedPath);
+ assert_array_equals(log.pathAtTargets[7], expectedPath);
+
+ assert_equals(log.event.target, nodes.A);
+ assert_equals(log.event.currentTarget, null);
+ }, 'The event must propagate out of ' + mode + ' mode shadow boundaries when the composed flag is set');
+}
+
+testComposedEventInDocument('open');
+testComposedEventInDocument('closed');
+
+/*
+-SR: ShadowRoot -S: Slot target: (~) relatedTarget: [~] *: indicates start digit: event path order
+window
+ + document
+ + body
+ + A ------------------------------- A-SR (3)
+ + B ------------ B-SR + A1 (2) -------- A1-SR (1)
+ + C + B1 --- B1-SR + A2-S [*; 0-3] + A1a (*; 0)
+ + D --- D-SR + B1a + B1b --- B1b-SR
+ + D1 + B1c-S + B1b1
+ + B1b2
+*/
+
+function testComposedEventWithRelatedTargetInDocument(mode) {
+ test(() => {
+ const nodes = createTestTree(mode);
+ document.body.appendChild(nodes.A);
+
+ const log = dispatchEventWithLog(nodes, nodes.A1a, new MouseEvent('foo', {composed: true, bubbles: true, relatedTarget: nodes['A2-S']}));
+ let expectedPath = ['A1a', 'A1-SR', 'A1', 'A-SR'];
+
+ assert_array_equals(log.eventPath, expectedPath);
+ assert_array_equals(log.eventPath.length, log.pathAtTargets.length);
+ assert_array_equals(log.pathAtTargets[0], expectedPath);
+ assert_array_equals(log.pathAtTargets[1], expectedPath);
+ if (mode != 'open')
+ expectedPath = expectedPath.slice(expectedPath.indexOf('A1'));
+ assert_array_equals(log.pathAtTargets[2], expectedPath);
+ assert_array_equals(log.pathAtTargets[3], expectedPath);
+ assert_array_equals(log.relatedTargets, ['A2-S', 'A2-S', 'A2-S', 'A2-S']);
+ }, 'The event must not propagate out of ' + mode + ' mode shadow tree in which the relative target and the relative related target are the same');
+}
+
+testComposedEventWithRelatedTargetInDocument('open');
+testComposedEventWithRelatedTargetInDocument('closed');
+
+</script>
+</body>
+</html>
Modified: trunk/LayoutTests/fast/shadow-dom/resources/event-path-test-helpers.js (208640 => 208641)
--- trunk/LayoutTests/fast/shadow-dom/resources/event-path-test-helpers.js 2016-11-12 04:00:55 UTC (rev 208640)
+++ trunk/LayoutTests/fast/shadow-dom/resources/event-path-test-helpers.js 2016-11-12 05:03:27 UTC (rev 208641)
@@ -7,7 +7,7 @@
var attachedNodes = [];
for (var nodeKey in shadow) {
var startingNode = shadow[nodeKey];
- for (var node = startingNode; node; node = node.parentNode) {
+ for (var node = startingNode; node; node = node == document ? window : node.parentNode) {
if (attachedNodes.indexOf(node) >= 0)
continue;
attachedNodes.push(node);
@@ -25,7 +25,7 @@
target.dispatchEvent(event);
- return {eventPath: eventPath, relatedTargets: relatedTargets, pathAtTargets: pathAtTargets};
+ return {event: event, eventPath: eventPath, relatedTargets: relatedTargets, pathAtTargets: pathAtTargets};
}
/*
Modified: trunk/Source/WebCore/ChangeLog (208640 => 208641)
--- trunk/Source/WebCore/ChangeLog 2016-11-12 04:00:55 UTC (rev 208640)
+++ trunk/Source/WebCore/ChangeLog 2016-11-12 05:03:27 UTC (rev 208641)
@@ -1,3 +1,37 @@
+2016-11-11 Ryosuke Niwa <[email protected]>
+
+ event.composedPath() does not include window
+ https://bugs.webkit.org/show_bug.cgi?id=164609
+ <rdar://problem/29210383>
+
+ Reviewed by Antti Koivisto.
+
+ Fixed the bug by including WindowContext be a part of the regular EventPath. This also simplifies
+ dispatchEventInDOM which used to had a special logic for dispatching an event on the window.
+
+ Also fixed a bug in EventDispatcher::dispatchEvent that event.target would be nullptr when an event was
+ dispatched inside a disconnected shadow tree or prevented from propagating to the document tree.
+ Preserve the final target by simply saving event.target() prior to invoking the default event handler instead.
+
+ Test: fast/shadow-dom/event-path-with-window.html
+
+ * dom/EventDispatcher.cpp:
+ (WebCore::WindowEventContext): Deleted. Moved to EventPath.cpp.
+ (WebCore::dispatchEventInDOM): Removed the code for WindowContext. The generic event dispatching logic
+ will do the same work now.
+ (WebCore::EventDispatcher::dispatchEvent): Restore the original target instead of using that of WindowContext.
+ * dom/EventPath.cpp:
+ (WebCore::WindowEventContext): Moved from EventDispatcher.cpp. Also made it a subclass of EventContext.
+ (WebCore::WindowEventContext::handleLocalEvents): Added.
+ (WebCore::EventPath::EventPath): When the parent's nullptr, check if the current node is Document. If it is,
+ follow https://dom.spec.whatwg.org/#interface-document where it says:
+ "A document’s get the parent algorithm, given an event, returns null if event’s type attribute value is 'load'
+ or document does not have a browsing context, and the document’s associated Window object otherwise."
+ (WebCore::EventPath::setRelatedTarget): Skip over WindowContext.
+ (WebCore::EventPath::retargetTouch): Ditto.
+ (WebCore::EventPath::computePathUnclosedToTarget): When the target is DOMWindow, use its document as the target.
+ Also, include any event target that is not a node in the event path.
+
2016-11-11 Dave Hyatt <[email protected]>
[CSS Parser] Support all the correct blend modes
Modified: trunk/Source/WebCore/dom/EventDispatcher.cpp (208640 => 208641)
--- trunk/Source/WebCore/dom/EventDispatcher.cpp 2016-11-12 04:00:55 UTC (rev 208640)
+++ trunk/Source/WebCore/dom/EventDispatcher.cpp 2016-11-12 05:03:27 UTC (rev 208641)
@@ -39,40 +39,6 @@
namespace WebCore {
-class WindowEventContext {
-public:
- WindowEventContext(Node*, const EventContext*);
-
- DOMWindow* window() const { return m_window.get(); }
- EventTarget* target() const { return m_target.get(); }
- bool handleLocalEvents(Event&);
-
-private:
- RefPtr<DOMWindow> m_window;
- RefPtr<EventTarget> m_target;
-};
-
-WindowEventContext::WindowEventContext(Node* node, const EventContext* topEventContext)
-{
- Node* topLevelContainer = topEventContext ? topEventContext->node() : node;
- if (!is<Document>(*topLevelContainer))
- return;
-
- m_window = downcast<Document>(*topLevelContainer).domWindow();
- m_target = topEventContext ? topEventContext->target() : node;
-}
-
-bool WindowEventContext::handleLocalEvents(Event& event)
-{
- if (!m_window)
- return false;
-
- event.setTarget(m_target.copyRef());
- event.setCurrentTarget(m_window.get());
- m_window->fireEventListeners(event);
- return true;
-}
-
void EventDispatcher::dispatchScopedEvent(Node& node, Event& event)
{
// We need to set the target here because it can go away by the time we actually fire the event.
@@ -101,17 +67,11 @@
}
}
-static void dispatchEventInDOM(Event& event, const EventPath& path, WindowEventContext& windowEventContext)
+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);
- // We don't dispatch load events to the window. This quirk was originally
- // added because Mozilla doesn't propagate load events to the window object.
- bool shouldFireEventAtWindow = event.type() != eventNames().loadEvent;
- if (shouldFireEventAtWindow && windowEventContext.handleLocalEvents(event) && event.propagationStopped())
- return;
-
for (size_t i = path.size() - 1; i > 0; --i) {
const EventContext& eventContext = path.contextAt(i);
if (eventContext.currentTargetSameAsTarget())
@@ -140,11 +100,6 @@
if (event.propagationStopped())
return;
}
- if (event.bubbles() && !event.cancelBubble()) {
- event.setEventPhase(Event::BUBBLING_PHASE);
- if (shouldFireEventAtWindow)
- windowEventContext.handleLocalEvents(event);
- }
}
bool EventDispatcher::dispatchEvent(Node* origin, Event& event)
@@ -171,8 +126,6 @@
ASSERT_WITH_SECURITY_IMPLICATION(!NoEventDispatchAssertion::isEventDispatchForbidden());
- WindowEventContext windowEventContext(node.get(), eventPath.lastContextIfExists());
-
InputElementClickState clickHandlingState;
if (is<HTMLInputElement>(*node))
downcast<HTMLInputElement>(*node).willDispatchEvent(event, clickHandlingState);
@@ -179,10 +132,11 @@
if (!event.propagationStopped() && !eventPath.isEmpty()) {
event.setEventPath(eventPath);
- dispatchEventInDOM(event, eventPath, windowEventContext);
+ dispatchEventInDOM(event, eventPath);
event.clearEventPath();
}
+ auto* finalTarget = event.target();
event.setTarget(EventPath::eventTargetRespectingTargetRules(*node));
event.setCurrentTarget(nullptr);
event.resetPropagationFlags();
@@ -197,9 +151,7 @@
if (!event.defaultPrevented() && !event.defaultHandled())
callDefaultEventHandlersInTheBubblingOrder(event, eventPath);
- // Ensure that after event dispatch, the event's target object is the
- // outermost shadow DOM boundary.
- event.setTarget(windowEventContext.target());
+ event.setTarget(finalTarget);
event.setCurrentTarget(nullptr);
return !event.defaultPrevented();
Modified: trunk/Source/WebCore/dom/EventPath.cpp (208640 => 208641)
--- trunk/Source/WebCore/dom/EventPath.cpp 2016-11-12 04:00:55 UTC (rev 208640)
+++ trunk/Source/WebCore/dom/EventPath.cpp 2016-11-12 05:03:27 UTC (rev 208641)
@@ -21,6 +21,7 @@
#include "config.h"
#include "EventPath.h"
+#include "DOMWindow.h"
#include "Event.h"
#include "EventContext.h"
#include "EventNames.h"
@@ -32,6 +33,23 @@
namespace WebCore {
+class WindowEventContext final : public EventContext {
+public:
+ WindowEventContext(Node&, DOMWindow&, EventTarget*);
+ void handleLocalEvents(Event&) const final;
+};
+
+WindowEventContext::WindowEventContext(Node& node, DOMWindow& currentTarget, EventTarget* target)
+ : EventContext(&node, ¤tTarget, target)
+{ }
+
+void WindowEventContext::handleLocalEvents(Event& event) const
+{
+ event.setTarget(m_target.get());
+ event.setCurrentTarget(m_currentTarget.get());
+ m_currentTarget->fireEventListeners(event);
+}
+
static inline bool shouldEventCrossShadowBoundary(Event& event, ShadowRoot& shadowRoot, EventTarget& target)
{
Node* targetNode = target.toNode();
@@ -108,10 +126,18 @@
break;
ContainerNode* parent = node->parentNode();
- if (!parent)
+ if (UNLIKELY(!parent)) {
+ // https://dom.spec.whatwg.org/#interface-document
+ if (is<Document>(*node) && event.type() != eventNames().loadEvent) {
+ ASSERT(target);
+ if (auto* window = downcast<Document>(*node).domWindow())
+ m_path.append(std::make_unique<WindowEventContext>(*node, *window, target));
+ }
return;
+ }
- if (ShadowRoot* shadowRootOfParent = parent->shadowRoot()) {
+ auto* shadowRootOfParent = parent->shadowRoot();
+ if (UNLIKELY(shadowRootOfParent)) {
if (auto* assignedSlot = shadowRootOfParent->findAssignedSlot(*node)) {
// node is assigned to a slot. Continue dispatching the event at this slot.
parent = assignedSlot;
@@ -144,7 +170,10 @@
TreeScope* previousTreeScope = nullptr;
size_t originalEventPathSize = m_path.size();
for (unsigned contextIndex = 0; contextIndex < originalEventPathSize; contextIndex++) {
- auto& context = downcast<MouseOrFocusEventContext>(*m_path[contextIndex]);
+ auto& ambgiousContext = *m_path[contextIndex];
+ if (!is<MouseOrFocusEventContext>(ambgiousContext))
+ continue;
+ auto& context = downcast<MouseOrFocusEventContext>(ambgiousContext);
Node& currentTarget = *context.node();
TreeScope& currentTreeScope = currentTarget.treeScope();
@@ -187,8 +216,10 @@
if (UNLIKELY(previousTreeScope && ¤tTreeScope != previousTreeScope))
retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);
- Node* currentRelatedNode = retargeter.currentNode(currentTarget);
- downcast<TouchEventContext>(*context).touchList(touchListType)->append(touch.cloneWithNewTarget(currentRelatedNode));
+ if (is<TouchEventContext>(*context)) {
+ Node* currentRelatedNode = retargeter.currentNode(currentTarget);
+ downcast<TouchEventContext>(*context).touchList(touchListType)->append(touch.cloneWithNewTarget(currentRelatedNode));
+ }
previousTreeScope = ¤tTreeScope;
}
@@ -223,18 +254,25 @@
return false;
}
+// https://dom.spec.whatwg.org/#dom-event-composedpath
Vector<EventTarget*> EventPath::computePathUnclosedToTarget(const EventTarget& target) const
{
Vector<EventTarget*> path;
const Node* targetNode = const_cast<EventTarget&>(target).toNode();
- if (!targetNode)
- return path;
+ if (!targetNode) {
+ const DOMWindow* domWindow = const_cast<EventTarget&>(target).toDOMWindow();
+ if (!domWindow)
+ return path;
+ targetNode = domWindow->document();
+ ASSERT(targetNode);
+ }
for (auto& context : m_path) {
if (Node* nodeInPath = context->currentTarget()->toNode()) {
if (targetNode->isUnclosedNode(*nodeInPath))
path.append(context->currentTarget());
- }
+ } else
+ path.append(context->currentTarget());
}
return path;