Add Event.deepPath() and Event.scoped
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 12 Mar 2016 03:11:04 +0000 (03:11 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 12 Mar 2016 03:11:04 +0000 (03:11 +0000)
https://bugs.webkit.org/show_bug.cgi?id=153538
<rdar://problem/24363836>

Reviewed by Darin Adler.

Source/WebCore:

Added the support for deepPath(), scoped, and relatedTargetScoped on Event.prototype for shadow DOM:
http://w3c.github.io/webcomponents/spec/shadow/#extensions-to-event-interface
and updated the EventPath class to respect scoped and relatedTargetScoped flags as specified at:
http://w3c.github.io/webcomponents/spec/shadow/#get-the-parent

Tests: fast/shadow-dom/Extensions-to-Event-Interface.html
       fast/shadow-dom/trusted-event-scoped-flags.html

* bindings/scripts/CodeGeneratorJS.pm:
(GenerateConstructorDefinition): Added the support for Conditional for InitializedByEventConstructor.
* bindings/scripts/test/GObject/WebKitDOMTestEventConstructor.cpp:
* bindings/scripts/test/GObject/WebKitDOMTestEventConstructor.h:
* bindings/scripts/test/JS/JSTestEventConstructor.cpp:
* bindings/scripts/test/ObjC/DOMTestEventConstructor.h:
* bindings/scripts/test/ObjC/DOMTestEventConstructor.mm:
* bindings/scripts/test/TestEventConstructor.idl: Added a test case for using InitializedByEventConstructor
with Conditional.
* dom/Event.cpp:
(WebCore::Event::Event): Initialize m_scoped and m_relatedTargetScoped from EventInit dictionary.
(WebCore::Event::scoped): Added. Implements http://w3c.github.io/webcomponents/spec/shadow/#scoped-flag
(WebCore::Event::deepPath): Added.
* dom/Event.h:
(WebCore::Event::relatedTargetScoped): Added. Overridden by FocusEvent and MouseEvent to implement
http://w3c.github.io/webcomponents/spec/shadow/#relatedtargetscoped-flag
(WebCore::Event::setEventPath): Added.
(WebCore::Event::clearEventPath): Added.
* dom/Event.idl: Added scoped, relatedTargetScoped, and deepPath() conditionally enabled for shadow DOM.
* dom/EventContext.h:
(WebCore::EventContext::currentTarget):
* dom/EventDispatcher.cpp:
(WebCore::EventDispatcher::dispatchEvent): Set the event path while the event is being dispatched.
* dom/EventPath.cpp:
(WebCore::shouldEventCrossShadowBoundary): Check event.scoped flag instead of hard-coding a list of events here
which has been moved to Event::scoped. See above.
(WebCore::EventPath::setRelatedTarget): Check m_event.relatedTargetScoped() instead of hard-coding a list of
events here. relatedTargetScoped is overridden by FocusEvent and MouseEvent.
(WebCore::EventPath::hasEventListeners): Fixed the misleading variable name.
(WebCore::isUnclosedNodeOf): Added. Implements http://w3c.github.io/webcomponents/spec/shadow/#dfn-unclosed-node
(WebCore::EventPath::computePathDisclosedToTarget): Added. Implements the algorithm to filter event targets:
http://w3c.github.io/webcomponents/spec/shadow/#widl-Event-deepPath-sequence-EventTarget
* dom/EventPath.h:
* dom/FocusEvent.cpp:
(WebCore::FocusEvent::relatedTargetScoped): Returns true when this is a trusted event per:
http://w3c.github.io/webcomponents/spec/shadow/#relatedtargetscoped-flag
* dom/FocusEvent.h:
* dom/MouseEvent.cpp:
(WebCore::MouseEvent::relatedTargetScoped): Ditto.
* dom/MouseEvent.h:

LayoutTests:

Added a W3C style testharness.js tests for Event.prototype.scoped, Event.prototype.scopedRelatedTarget,
Event.prototype.deepPath() and a test that uses eventSender to verify the values of the scoped and
scopedRelatedTarget flags on trusted events.

* fast/shadow-dom/Extensions-to-Event-Interface-expected.txt: Added.
* fast/shadow-dom/Extensions-to-Event-Interface.html: Added.
* fast/shadow-dom/event-with-related-target.html:
* fast/shadow-dom/resources: Added.
* fast/shadow-dom/resources/event-path-test-helpers.js: Added. Extracted from event-with-related-target.html.
* fast/shadow-dom/trusted-event-scoped-flags-expected.txt: Added.
* fast/shadow-dom/trusted-event-scoped-flags.html: Added.
* fast/xmlhttprequest/xmlhttprequest-get-expected.txt:
* http/tests/workers/worker-importScriptsOnError-expected.txt:
* inspector/model/remote-object-get-properties-expected.txt:
* platform/ios-simulator/fast/shadow-dom/trusted-event-scoped-flags-expected.txt: Added.

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

32 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/shadow-dom/Extensions-to-Event-Interface-expected.txt [new file with mode: 0644]
LayoutTests/fast/shadow-dom/Extensions-to-Event-Interface.html [new file with mode: 0644]
LayoutTests/fast/shadow-dom/event-with-related-target.html
LayoutTests/fast/shadow-dom/negative-tabindex-on-shadow-host-expected.txt
LayoutTests/fast/shadow-dom/negative-tabindex-on-shadow-host.html
LayoutTests/fast/shadow-dom/resources/event-path-test-helpers.js [new file with mode: 0644]
LayoutTests/fast/shadow-dom/trusted-event-scoped-flags-expected.txt [new file with mode: 0644]
LayoutTests/fast/shadow-dom/trusted-event-scoped-flags.html [new file with mode: 0644]
LayoutTests/fast/xmlhttprequest/xmlhttprequest-get-expected.txt
LayoutTests/http/tests/workers/worker-importScriptsOnError-expected.txt
LayoutTests/inspector/model/remote-object-get-properties-expected.txt
LayoutTests/platform/ios-simulator/fast/shadow-dom/trusted-event-scoped-flags-expected.txt [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/bindings/scripts/CodeGeneratorJS.pm
Source/WebCore/bindings/scripts/test/GObject/WebKitDOMTestEventConstructor.cpp
Source/WebCore/bindings/scripts/test/GObject/WebKitDOMTestEventConstructor.h
Source/WebCore/bindings/scripts/test/JS/JSTestEventConstructor.cpp
Source/WebCore/bindings/scripts/test/ObjC/DOMTestEventConstructor.h
Source/WebCore/bindings/scripts/test/ObjC/DOMTestEventConstructor.mm
Source/WebCore/bindings/scripts/test/TestEventConstructor.idl
Source/WebCore/dom/Event.cpp
Source/WebCore/dom/Event.h
Source/WebCore/dom/Event.idl
Source/WebCore/dom/EventContext.h
Source/WebCore/dom/EventDispatcher.cpp
Source/WebCore/dom/EventPath.cpp
Source/WebCore/dom/EventPath.h
Source/WebCore/dom/FocusEvent.cpp
Source/WebCore/dom/FocusEvent.h
Source/WebCore/dom/MouseEvent.cpp
Source/WebCore/dom/MouseEvent.h

index 64de8c2..01f1141 100644 (file)
@@ -1,3 +1,27 @@
+2016-03-11  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Add Event.deepPath() and Event.scoped
+        https://bugs.webkit.org/show_bug.cgi?id=153538
+        <rdar://problem/24363836>
+
+        Reviewed by Darin Adler.
+
+        Added a W3C style testharness.js tests for Event.prototype.scoped, Event.prototype.scopedRelatedTarget,
+        Event.prototype.deepPath() and a test that uses eventSender to verify the values of the scoped and
+        scopedRelatedTarget flags on trusted events.
+
+        * fast/shadow-dom/Extensions-to-Event-Interface-expected.txt: Added.
+        * fast/shadow-dom/Extensions-to-Event-Interface.html: Added.
+        * fast/shadow-dom/event-with-related-target.html:
+        * fast/shadow-dom/resources: Added.
+        * fast/shadow-dom/resources/event-path-test-helpers.js: Added. Extracted from event-with-related-target.html.
+        * fast/shadow-dom/trusted-event-scoped-flags-expected.txt: Added.
+        * fast/shadow-dom/trusted-event-scoped-flags.html: Added.
+        * fast/xmlhttprequest/xmlhttprequest-get-expected.txt:
+        * http/tests/workers/worker-importScriptsOnError-expected.txt:
+        * inspector/model/remote-object-get-properties-expected.txt:
+        * platform/ios-simulator/fast/shadow-dom/trusted-event-scoped-flags-expected.txt: Added.
+
 2016-03-11  Jiewen Tan  <jiewen_tan@apple.com>
 
         WebKit should not be redirected to an invalid URL
diff --git a/LayoutTests/fast/shadow-dom/Extensions-to-Event-Interface-expected.txt b/LayoutTests/fast/shadow-dom/Extensions-to-Event-Interface-expected.txt
new file mode 100644 (file)
index 0000000..9f02128
--- /dev/null
@@ -0,0 +1,25 @@
+
+PASS deepPath() must exist on Event 
+PASS deepPath() must return an empty array when the event has not been dispatched 
+PASS deepPath() must return an empty array when the event is no longer dispatched 
+PASS scoped must exist on Event 
+PASS scoped on EventInit must default to false 
+PASS scoped on EventInit must set the scoped flag 
+PASS relatedTargetScoped must exist on Event 
+PASS relatedTargetScoped on EventInit must default to false 
+PASS relatedTargetScoped on EventInit must set the scoped flag 
+PASS The event must propagate out of open mode shadow boundaries when the scoped flag is unset 
+PASS The event must propagate out of closed mode shadow boundaries when the scoped flag is unset 
+PASS The event must not propagate out of open mode shadow boundaries when the scoped flag is set 
+PASS The event must not propagate out of closed mode shadow boundaries when the scoped flag is set 
+PASS The event must propagate out of open mode shadow boundaries when the scoped flag is unset on an event with relatedTarget 
+PASS The event must propagate out of closed mode shadow boundaries when the scoped flag is unset on an event with relatedTarget 
+PASS The event must not propagate out of open mode shadow boundaries when the scoped flag is set on an event with relatedTarget 
+PASS The event must not propagate out of closed mode shadow boundaries when the scoped flag is set on an event with relatedTarget 
+PASS The event must not propagate out of open mode shadow tree of the target but must propagate out of inner shadow trees when the scoped flag is set 
+PASS The event must not propagate out of closed mode shadow tree of the target but must propagate out of inner shadow trees when the scoped flag is set 
+PASS The event must propagate out of open mode shadow tree in which the relative target and the relative related target are the same 
+PASS The event must propagate out of closed mode shadow tree in which the relative target and the relative related target are the same 
+PASS deepPath() must contain and only contain the unclosed nodes of target in open mode shadow trees 
+PASS deepPath() must contain and only contain the unclosed nodes of target in closed mode shadow trees 
+
diff --git a/LayoutTests/fast/shadow-dom/Extensions-to-Event-Interface.html b/LayoutTests/fast/shadow-dom/Extensions-to-Event-Interface.html
new file mode 100644 (file)
index 0000000..7575b67
--- /dev/null
@@ -0,0 +1,285 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Shadow DOM: Extensions to Event Interface</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="Event interface must have deepPath() as a method">
+<link rel="help" href="http://w3c.github.io/webcomponents/spec/shadow/#extensions-to-event-interface">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="resources/event-path-test-helpers.js"></script>
+<link rel='stylesheet' href='../../resources/testharness.css'>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+test(function () {
+    assert_true('deepPath' in Event.prototype);
+    assert_true('deepPath' in new Event('my-event'));
+}, 'deepPath() must exist on Event');
+
+test(function () {
+    var event = new Event('my-event');
+    assert_array_equals(event.deepPath(), []);
+}, 'deepPath() must return an empty array when the event has not been dispatched');
+
+test(function () {
+    var event = new Event('my-event');
+    document.body.dispatchEvent(event);
+    assert_array_equals(event.deepPath(), []);
+}, 'deepPath() must return an empty array when the event is no longer dispatched');
+
+test(function () {
+    assert_true('scoped' in Event.prototype);
+    assert_true('scoped' in new Event('my-event'));
+}, 'scoped must exist on Event');
+
+test(function () {
+    var event = new Event('my-event');
+    assert_false(event.scoped);
+}, 'scoped on EventInit must default to false');
+
+test(function () {
+    var event = new Event('my-event', {scoped: true});
+    assert_true(event.scoped);
+
+    event = new Event('my-event', {scoped: false});
+    assert_false(event.scoped);
+}, 'scoped on EventInit must set the scoped flag');
+
+test(function () {
+    assert_true('relatedTargetScoped' in Event.prototype);
+    assert_true('relatedTargetScoped' in new Event('my-event'));
+}, 'relatedTargetScoped must exist on Event');
+
+test(function () {
+    var event = new Event('my-event');
+    assert_false(event.relatedTargetScoped);
+}, 'relatedTargetScoped on EventInit must default to false');
+
+test(function () {
+    var event = new Event('my-event', {relatedTargetScoped: true});
+    assert_true(event.relatedTargetScoped);
+
+    event = new Event('my-event', {relatedTargetScoped: false});
+    assert_false(event.relatedTargetScoped);
+}, 'relatedTargetScoped on EventInit must set the scoped flag');
+
+/*
+-SR: ShadowRoot  -S: Slot  target: (~)  *: indicates start  digit: event path order
+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 testUnscopedEvent(mode) {
+    test(function () {
+        var nodes = createTestTree(mode);
+        var log = dispatchEventWithLog(nodes, nodes.A1a, new Event('my-event', {scoped: false, bubbles: true}));
+
+        var expectedPath = ['A1a', 'A1-SR', 'A1', 'A-SR', 'A'];
+        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_array_equals(log.pathAtTargets[2], mode == 'open' ? expectedPath : ['A1', 'A-SR', 'A'],
+            'deepPath must only contain unclosed nodes of the current target.');
+    }, 'The event must propagate out of ' + mode + ' mode shadow boundaries when the scoped flag is unset');
+}
+
+testUnscopedEvent('open');
+testUnscopedEvent('closed');
+
+/*
+-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 testScopedEvent(mode) {
+    test(function () {
+        var nodes = createTestTree(mode);
+        var log = dispatchEventWithLog(nodes, nodes.A1a, new Event('my-event', {scoped: true, bubbles: true}));
+
+        var 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);
+    }, 'The event must not propagate out of ' + mode + ' mode shadow boundaries when the scoped flag is set');
+}
+
+testScopedEvent('open');
+testScopedEvent('closed');
+
+/*
+-SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+A (4) [4] ----------------------- 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 testUnscopedEventWithUnscopedRelatedTarget(mode) {
+    test(function () {
+        var nodes = createTestTree(mode);
+        var log = dispatchEventWithLog(nodes, nodes.A1a, new MouseEvent('foo', {scoped: false, relatedTargetScoped: false, bubbles: true, relatedTarget: nodes['A2-S']}));
+
+        var expectedPath = ['A1a', 'A1-SR', 'A1', 'A-SR', 'A'];
+        var pathExposedToA1 = ['A1', 'A-SR', 'A'];
+        var pathExposedToA = ['A'];
+        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_array_equals(log.pathAtTargets[2], mode == 'open' ? expectedPath : pathExposedToA1);
+        assert_array_equals(log.pathAtTargets[3], mode == 'open' ? expectedPath : pathExposedToA1);
+        assert_array_equals(log.pathAtTargets[4], mode == 'open' ? expectedPath : pathExposedToA);
+        assert_array_equals(log.relatedTargets, ['A2-S', 'A2-S', 'A2-S', 'A2-S', 'A']);
+    }, 'The event must propagate out of ' + mode + ' mode shadow boundaries when the scoped flag is unset on an event with relatedTarget');
+}
+
+testUnscopedEventWithUnscopedRelatedTarget('open');
+testUnscopedEventWithUnscopedRelatedTarget('closed');
+
+/*
+-SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+A ------------------------------- A-SR
++ B ------------ B-SR             + A1 ----------- A1-SR (1)
+  + C            + B1 --- B1-SR   + A2-S [*; 0-1]  + A1a (*; 0)
+  + D --- D-SR     + B1a  + B1b --- B1b-SR
+          + D1            + B1c-S   + B1b1
+                                    + B1b2
+*/
+
+function testScopedEventWithUnscopedRelatedTarget(mode) {
+    test(function () {
+        var nodes = createTestTree(mode);
+        var log = dispatchEventWithLog(nodes, nodes.A1a, new MouseEvent('foo', {scoped: true, relatedTargetScoped: false, bubbles: true, relatedTarget: nodes['A2-S']}));
+
+        var 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_array_equals(log.relatedTargets, ['A2-S', 'A2-S']);
+    }, 'The event must not propagate out of ' + mode + ' mode shadow boundaries when the scoped flag is set on an event with relatedTarget');
+}
+
+testScopedEventWithUnscopedRelatedTarget('open');
+testScopedEventWithUnscopedRelatedTarget('closed');
+
+/*
+-SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+A ------------------------------------------------ A-SR
++ B ------------ B-SR (4)                          + A1 --- A1-SR
+  + C            + B1 (3) [0,3-4] --- B1-SR (2)    + A2-S   + A1a
+  + D --- D-SR     + B1a (*; 0)       + B1b [1-2] --- B1b-SR
+          + D1                        + B1c-S (1)     + B1b1
+                                                      + B1b2 [*]
+*/
+
+function testScopedEventWithUnscopedRelatedTargetThroughSlot(mode) {
+    test(function () {
+        var nodes = createTestTree(mode);
+        var log = dispatchEventWithLog(nodes, nodes.B1a, new MouseEvent('foo', {scoped: true, relatedTargetScoped: false, bubbles: true, relatedTarget: nodes['B1b2']}));
+
+        var expectedPath = ['B1a', 'B1c-S', 'B1-SR', 'B1', 'B-SR'];
+        var pathExposedToB1a = ['B1a', 'B1', 'B-SR'];
+        assert_array_equals(log.eventPath, expectedPath);
+        assert_array_equals(log.eventPath.length, log.pathAtTargets.length);
+        assert_array_equals(log.pathAtTargets[0], mode == 'open' ? expectedPath : pathExposedToB1a);
+        assert_array_equals(log.pathAtTargets[1], expectedPath);
+        assert_array_equals(log.pathAtTargets[2], expectedPath);
+        assert_array_equals(log.pathAtTargets[3], mode == 'open' ? expectedPath : pathExposedToB1a);
+        assert_array_equals(log.pathAtTargets[4], mode == 'open' ? expectedPath : pathExposedToB1a);
+        assert_array_equals(log.relatedTargets, ['B1', 'B1b', 'B1b', 'B1', 'B1']);
+    }, 'The event must not propagate out of ' + mode + ' mode shadow tree of the target but must propagate out of inner shadow trees when the scoped flag is set');
+}
+
+testScopedEventWithUnscopedRelatedTargetThroughSlot('open');
+testScopedEventWithUnscopedRelatedTargetThroughSlot('closed');
+
+/*
+-SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+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 testUnscopedEventWithScopedRelatedTarget(mode) {
+    test(function () {
+        var nodes = createTestTree(mode);
+        log = dispatchEventWithLog(nodes, nodes.A1a, new MouseEvent('foo', {scoped: false, relatedTargetScoped: true, bubbles: true, relatedTarget: nodes['A2-S']}));
+
+        var expectedPath = ['A1a', 'A1-SR', 'A1', 'A-SR'];
+        var pathExposedToA1 = ['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);
+        assert_array_equals(log.pathAtTargets[2], mode == 'open' ? expectedPath : pathExposedToA1);
+        assert_array_equals(log.pathAtTargets[3], mode == 'open' ? expectedPath : pathExposedToA1);
+        assert_array_equals(log.relatedTargets, ['A2-S', 'A2-S', 'A2-S', 'A2-S']);
+    }, 'The event must propagate out of ' + mode + ' mode shadow tree in which the relative target and the relative related target are the same');
+}
+
+testUnscopedEventWithScopedRelatedTarget('open');
+testUnscopedEventWithScopedRelatedTarget('closed');
+
+/*
+-SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
+A (8) [0-5,8] ---------------------------------------- A-SR (7)
++ B (5)  ------- B-SR (4)                              + A1 [6,7] --- A1-SR
+  + C            + B1 (3) ------- B1-SR (2)            + A2-S (6)     + A1a [*]
+  + D --- D-SR     + B1a (*; 0)   + B1b ------- B1b-SR
+          + D1                    + B1c-S (1)   + B1b1
+                                                + B1b2
+*/
+
+function testUnscopedEventWithScopedRelatedTargetThroughSlot(mode) {
+    test(function () {
+        var nodes = createTestTree(mode);
+        log = dispatchEventWithLog(nodes, nodes.B1a, new MouseEvent('foo', {scoped: false, relatedTargetScoped: true, bubbles: true, relatedTarget: nodes.A1a}));
+
+        var expectedPath =          ['B1a', 'B1c-S', 'B1-SR', 'B1', 'B-SR', 'B', 'A2-S', 'A-SR', 'A'];
+        var expectedRelatedTarget = ['A',   'A',     'A',     'A',   'A',   'A', 'A1',   'A1',   'A'];
+        var pathExposedToB1a =      ['B1a',                   'B1', 'B-SR', 'B',                 'A'];
+        var pathExposedToB1cS =     ['B1a', 'B1c-S', 'B1-SR', 'B1', 'B-SR', 'B',                 'A'];
+        var pathExposedToB =        [                                       'B',                 'A'];
+        var pathExposedToA1 =       [                                       'B', 'A2-S', 'A-SR', 'A'];
+
+        assert_array_equals(log.eventPath, expectedPath);
+        assert_array_equals(log.eventPath.length, log.pathAtTargets.length);
+        assert_array_equals(log.pathAtTargets[0], mode == 'open' ? expectedPath : pathExposedToB1a);
+        assert_array_equals(log.pathAtTargets[1], mode == 'open' ? expectedPath : pathExposedToB1cS);
+        assert_array_equals(log.pathAtTargets[2], mode == 'open' ? expectedPath : pathExposedToB1cS);
+        assert_array_equals(log.pathAtTargets[3], mode == 'open' ? expectedPath : pathExposedToB1a);
+        assert_array_equals(log.pathAtTargets[4], mode == 'open' ? expectedPath : pathExposedToB1a);
+        assert_array_equals(log.pathAtTargets[5], mode == 'open' ? expectedPath : pathExposedToB);
+        assert_array_equals(log.pathAtTargets[6], mode == 'open' ? expectedPath : pathExposedToA1);
+        assert_array_equals(log.pathAtTargets[7], mode == 'open' ? expectedPath : pathExposedToA1);
+        assert_array_equals(log.pathAtTargets[8], mode == 'open' ? expectedPath : pathExposedToB);
+        assert_array_equals(log.relatedTargets, expectedRelatedTarget);
+    }, 'deepPath() must contain and only contain the unclosed nodes of target in ' + mode + ' mode shadow trees');
+}
+
+testUnscopedEventWithScopedRelatedTargetThroughSlot('open');
+testUnscopedEventWithScopedRelatedTargetThroughSlot('closed');
+
+</script>
+</body>
+</html>
index aec7113..112bb06 100644 (file)
@@ -7,99 +7,13 @@
     <link rel="help" href="https://w3c.github.io/webcomponents/spec/shadow/#retargeting-relatedtarget">
     <script src="../../resources/testharness.js"></script>
     <script src="../../resources/testharnessreport.js"></script>
+    <script src="resources/event-path-test-helpers.js"></script>
     <link rel='stylesheet' href='../../resources/testharness.css'>
 </head>
 <body>
     <div id="log"></div>
     <script>
 
-        function dispatchEventWithLog(shadow, target, event) {
-            var eventPath = [];
-            var relatedTargets = [];
-
-            var attachedNodes = [];
-            for (var nodeKey in shadow) {
-                var startingNode = shadow[nodeKey];
-                for (var node = startingNode; node; node = node.parentNode) {
-                    if (attachedNodes.indexOf(node) >= 0)
-                        continue;
-                    attachedNodes.push(node);
-                    node.addEventListener(event.type, (function (event) {
-                        eventPath.push(this.label);
-                        relatedTargets.push(event.relatedTarget.label);
-                    }).bind(node));
-                }
-            }
-
-            target.dispatchEvent(event);
-
-            return {eventPath: eventPath, relatedTargets: relatedTargets};
-        }
-
-        /*
-        -SR: ShadowRoot  -S: Slot
-        A ------------------------------- A-SR
-        + B ------------ B-SR             + A1 --- A1-SR
-          + C            + B1 --- B1-SR   + A2-S   + A1a
-          + D --- D-SR   + B1a    + B1b --- B1b-SR
-                  + D1            + B1c-S   + B1b1
-                                            + B1b2
-        */
-        function createTestTree(mode) {
-            var namedNodes = {};
-
-            function element(name) {
-                var element = document.createElement(name.indexOf('-S') > 0 ? 'slot' : 'div');
-                element.label = name;
-                namedNodes[name] = element;
-                for (var i = 1; i < arguments.length; i++) {
-                    var item = arguments[i];
-                    if (typeof(item) == 'function')
-                        item(element);
-                    else
-                        element.appendChild(item);
-                }
-                return element;
-            }
-
-            function shadow(name) {
-                var children = [];
-                for (var i = 1; i < arguments.length; i++)
-                    children.push(arguments[i]);
-                return function (element) {
-                    var shadowRoot = element.attachShadow({mode: mode});
-                    shadowRoot.label = name;
-                    namedNodes[name] = shadowRoot;
-                    for (var child of children)
-                        shadowRoot.appendChild(child);
-                }
-            }
-
-            var host = element('A',
-                shadow('A-SR',
-                    element('A1',
-                        shadow('A1-SR',
-                            element('A1a'))),
-                    element('A2-S')
-                ),
-                element('B',
-                    shadow('B-SR',
-                        element('B1',
-                            shadow('B1-SR',
-                                element('B1b',
-                                    shadow('B1b-SR',
-                                        element('B1b1'),
-                                        element('B1b2'))),
-                                element('B1c-S')),
-                            element('B1a'))),
-                    element('C'),
-                    element('D',
-                        shadow('D-SR',
-                            element('D1')))));
-
-            return namedNodes;
-        }
-
         /*
         -SR: ShadowRoot  -S: Slot  target: (~)  relatedTarget: [~]  *: indicates start  digit: event path order
         A (8) --------------------------------------------- A-SR (7)
index c3d90df..1551d4e 100644 (file)
@@ -2,9 +2,8 @@ Tests for setting a negative tabindex on shadow host. Elements inside such a sha
 To manually test, press tab key four times. It should traverse focusable elements in the increasing numerical order.
 
 1. First sequentially focusable element outside shadow trees
-2. / 3.2. Shadow host with a positive tabindex
-3.1. Focusable element inside a shadow host with a positive tabindex
-2. / 3.2. Shadow host with a positive tabindex
+2. Shadow host with a positive tabindex
+3. Focusable element inside a shadow host with a positive tabindex
 4.1. Focusable element inside a shadow host with no tabindex
 4.2. Shadow host with no tabindex
 5. Last sequentially focusable element outside shadow trees
index 13b2b21..5398607 100644 (file)
@@ -7,7 +7,7 @@ To manually test, press tab key four times. It should traverse focusable element
 <div id="first" tabindex="1" onfocus="log(this)">1. First sequentially focusable element outside shadow trees</div>
 <div id="host-with-negative-tabindex" tabindex="-1" onfocus="log(this)">Shadow host with a negative tabindex</div>
 <div id="host-with-no-tabindex" onfocus="log(this)">4.2. Shadow host with no tabindex</div>
-<div id="host-with-positive-tabindex" tabindex="2" onfocus="log(this)">2. / 3.2. Shadow host with a positive tabindex</div>
+<div id="host-with-positive-tabindex" tabindex="2" onfocus="log(this)">2. Shadow host with a positive tabindex</div>
 <div tabindex="0" onfocus="log(this)">5. Last sequentially focusable element outside shadow trees</div>
 </div>
 <pre></pre>
@@ -23,7 +23,7 @@ document.getElementById('host-with-no-tabindex').attachShadow({mode: 'closed'}).
 
 document.getElementById('host-with-positive-tabindex').attachShadow({mode: 'closed'}).innerHTML = `
     <slot></slot>
-    <div tabindex="0" onfocus="log(this)">3.1. Focusable element inside a shadow host with a positive tabindex</div>
+    <div tabindex="0" onfocus="log(this)">3. Focusable element inside a shadow host with a positive tabindex</div>
 `;
 
 function log(element) {
diff --git a/LayoutTests/fast/shadow-dom/resources/event-path-test-helpers.js b/LayoutTests/fast/shadow-dom/resources/event-path-test-helpers.js
new file mode 100644 (file)
index 0000000..a1c494a
--- /dev/null
@@ -0,0 +1,93 @@
+
+function dispatchEventWithLog(shadow, target, event) {
+    var eventPath = [];
+    var relatedTargets = [];
+    var pathAtTargets = [];
+
+    var attachedNodes = [];
+    for (var nodeKey in shadow) {
+        var startingNode = shadow[nodeKey];
+        for (var node = startingNode; node; node = node.parentNode) {
+            if (attachedNodes.indexOf(node) >= 0)
+                continue;
+            attachedNodes.push(node);
+            node.addEventListener(event.type, (function (event) {
+                eventPath.push(this.label);
+                relatedTargets.push(event.relatedTarget ? event.relatedTarget.label : null);
+
+                if (!event.deepPath) // Don't fail all tests just for the lack of deepPath.
+                    return;
+
+                pathAtTargets.push(event.deepPath().map(function (node) { return node.label; }));
+            }).bind(node));
+        }
+    }
+
+    target.dispatchEvent(event);
+
+    return {eventPath: eventPath, relatedTargets: relatedTargets, pathAtTargets: pathAtTargets};
+}
+
+/*
+-SR: ShadowRoot  -S: Slot
+A ------------------------------- A-SR
++ B ------------ B-SR             + A1 --- A1-SR
+  + C            + B1 --- B1-SR   + A2-S   + A1a
+  + D --- D-SR     + B1a  + B1b --- B1b-SR
+          + D1            + B1c-S   + B1b1
+                                    + B1b2
+*/
+function createTestTree(mode) {
+    var namedNodes = {};
+
+    function element(name) {
+        var element = document.createElement(name.indexOf('-S') > 0 ? 'slot' : 'div');
+        element.label = name;
+        namedNodes[name] = element;
+        for (var i = 1; i < arguments.length; i++) {
+            var item = arguments[i];
+            if (typeof(item) == 'function')
+                item(element);
+            else
+                element.appendChild(item);
+        }
+        return element;
+    }
+
+    function shadow(name) {
+        var children = [];
+        for (var i = 1; i < arguments.length; i++)
+            children.push(arguments[i]);
+        return function (element) {
+            var shadowRoot = element.attachShadow({mode: mode});
+            shadowRoot.label = name;
+            namedNodes[name] = shadowRoot;
+            for (var child of children)
+                shadowRoot.appendChild(child);
+        }
+    }
+
+    var host = element('A',
+        shadow('A-SR',
+            element('A1',
+                shadow('A1-SR',
+                    element('A1a'))),
+            element('A2-S')
+        ),
+        element('B',
+            shadow('B-SR',
+                element('B1',
+                    shadow('B1-SR',
+                        element('B1b',
+                            shadow('B1b-SR',
+                                element('B1b1'),
+                                element('B1b2'))),
+                        element('B1c-S')),
+                    element('B1a'))),
+            element('C'),
+            element('D',
+                shadow('D-SR',
+                    element('D1')))));
+
+    return namedNodes;
+}
diff --git a/LayoutTests/fast/shadow-dom/trusted-event-scoped-flags-expected.txt b/LayoutTests/fast/shadow-dom/trusted-event-scoped-flags-expected.txt
new file mode 100644 (file)
index 0000000..3abbb8f
--- /dev/null
@@ -0,0 +1,67 @@
+
+world
+PASS input.type = "radio"; log(input, "change"); input.click(); eventType is "change"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS log(form, "reset"); form.reset(); eventType is "reset"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS form.focus(); log(input, "focus"); input.focus(); eventType is "focus"
+PASS scoped is false
+PASS relatedTargetScoped is true
+
+PASS log(input, "blur"); form.focus(); eventType is "blur"
+PASS scoped is false
+PASS relatedTargetScoped is true
+
+PASS input.type = "text"; log(input, "mousemove"); eventSender.mouseMoveTo(x, y); eventType is "mousemove"
+PASS scoped is false
+PASS relatedTargetScoped is false
+
+PASS log(input, "mousedown"); eventSender.mouseDown(); eventType is "mousedown"
+PASS scoped is false
+PASS relatedTargetScoped is false
+
+PASS log(input, "mouseup"); eventSender.mouseUp(); eventType is "mouseup"
+PASS scoped is false
+PASS relatedTargetScoped is false
+
+PASS log(input, "mouseout"); eventSender.mouseMoveTo(0, 0); eventType is "mouseout"
+PASS scoped is false
+PASS relatedTargetScoped is true
+
+PASS log(input, "mouseover"); eventSender.mouseMoveTo(x, y); eventType is "mouseover"
+PASS scoped is false
+PASS relatedTargetScoped is true
+
+input.value = "hello"; eventSender.mouseMoveTo(input.offsetLeft + 1, y); eventSender.mouseDown();
+PASS log(input, "select"); eventSender.mouseMoveTo(input.offsetLeft + input.offsetWidth - 2, y); eventSender.mouseUp(); eventType is "select"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS log(editableElement, "selectstart"); eventSender.mouseMoveTo(editableElement.offsetLeft + 1, y); eventSender.mouseDown(); eventType is "selectstart"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS eventType is "load"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS eventType is "error"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS eventType is "scroll"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS eventType is "resize"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/shadow-dom/trusted-event-scoped-flags.html b/LayoutTests/fast/shadow-dom/trusted-event-scoped-flags.html
new file mode 100644 (file)
index 0000000..08f4dc2
--- /dev/null
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<html>
+<body>
+<form><input></form>
+<div id="editor" contenteditable>world</div>
+<div id="console"></div>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+
+var eventType;
+var scoped;
+var relatedTargetScoped;
+
+function logEvent(event) {
+    eventType = event.type;
+    scoped = event.scoped;
+    relatedTargetScoped = event.relatedTargetScoped;
+}
+
+function checkFlags(code, expected) {
+    shouldBeEqualToString(code ? code + '; eventType' : 'eventType', expected.eventType);
+    shouldBe('scoped', expected.scoped.toString());
+    shouldBe('relatedTargetScoped', expected.relatedTargetScoped.toString());
+    debug('');
+}
+
+var lastTarget;
+var lastEventName;
+function log(target, eventName) {
+    eventType = undefined;
+    scoped = undefined;
+    relatedTargetScoped = undefined;
+    if (lastTarget)
+        lastTarget.removeEventListener(lastEventName, logEvent);
+    target.addEventListener(eventName, logEvent);
+    lastTarget = target;
+    lastEventName = eventName;
+}
+
+var input = document.querySelector('input');
+var form = document.querySelector('form');
+form.tabIndex = 0;
+
+checkFlags('input.type = "radio"; log(input, "change"); input.click()', {eventType: 'change', scoped: true, relatedTargetScoped: false});
+checkFlags('log(form, "reset"); form.reset()', {eventType: 'reset', scoped: true, relatedTargetScoped: false});
+
+checkFlags('form.focus(); log(input, "focus"); input.focus()', {eventType: 'focus', scoped: false, relatedTargetScoped: true});
+checkFlags('log(input, "blur"); form.focus()', {eventType: 'blur', scoped: false, relatedTargetScoped: true});
+
+if (!window.eventSender)
+    testFailed('This test requires eventSender');
+else {
+    testRunner.waitUntilDone();
+
+    eventSender.dragMode = false;
+
+    var x = input.offsetLeft + input.offsetWidth / 2;
+    var y = input.offsetTop + input.offsetHeight / 2;
+
+    checkFlags('input.type = "text"; log(input, "mousemove"); eventSender.mouseMoveTo(x, y)', {eventType: 'mousemove', scoped: false, relatedTargetScoped: false});
+    checkFlags('log(input, "mousedown"); eventSender.mouseDown()', {eventType: 'mousedown', scoped: false, relatedTargetScoped: false});
+    checkFlags('log(input, "mouseup"); eventSender.mouseUp()', {eventType: 'mouseup', scoped: false, relatedTargetScoped: false});
+    checkFlags('log(input, "mouseout"); eventSender.mouseMoveTo(0, 0)', {eventType: 'mouseout', scoped: false, relatedTargetScoped: true});
+    checkFlags('log(input, "mouseover"); eventSender.mouseMoveTo(x, y)', {eventType: 'mouseover', scoped: false, relatedTargetScoped: true});
+
+    evalAndLog('input.value = "hello"; eventSender.mouseMoveTo(input.offsetLeft + 1, y); eventSender.mouseDown();');
+    checkFlags('log(input, "select"); eventSender.mouseMoveTo(input.offsetLeft + input.offsetWidth - 2, y); eventSender.mouseUp()',
+        {eventType: 'select', scoped: true, relatedTargetScoped: false});
+
+    var editableElement = document.getElementById('editor');
+    y = editableElement.offsetTop + editableElement.offsetHeight / 2;
+    checkFlags('log(editableElement, "selectstart"); eventSender.mouseMoveTo(editableElement.offsetLeft + 1, y); eventSender.mouseDown()',
+        {eventType: 'selectstart', scoped: true, relatedTargetScoped: false});
+}
+
+function testLoadEvent() {
+    var scriptThatLoads = document.createElement('script');
+    scriptThatLoads.src = "resources/event-path-test-helpers.js";
+    scriptThatLoads.onload = function (event) {
+        logEvent(event);
+        checkFlags('', {eventType: 'load', scoped: true, relatedTargetScoped: false});
+        testErrorEvent();
+    }
+    document.body.appendChild(scriptThatLoads);
+}
+
+function testErrorEvent() {
+    var scriptThatFailsToLoad = document.createElement('script');
+    scriptThatFailsToLoad.src = "bad.js";
+    scriptThatFailsToLoad.onerror = function (event) {
+        logEvent(event);
+        checkFlags('', {eventType: 'error', scoped: true, relatedTargetScoped: false});
+        testScrollEvent();
+    }
+    document.body.appendChild(scriptThatFailsToLoad);
+}
+
+function testScrollEvent() {
+    document.body.style.marginBottom = '1000px';
+    log(window, 'scroll');
+    setTimeout(function () {
+        window.scrollTo(0, 1000);
+        setTimeout(function () {
+            checkFlags('', {eventType: 'scroll', scoped: true, relatedTargetScoped: false});
+            window.scrollTo(0, 0);
+            testResizeEvent();
+        }, 0);
+    }, 0);
+}
+
+function testResizeEvent() {
+    var iframe = document.createElement('iframe');
+    iframe.style.width = '100px';
+    iframe.style.height = '100px';
+
+    iframe.onload = function () {
+        iframe.contentDocument.body.getBoundingClientRect();
+        log(iframe.contentWindow, "resize");
+        setTimeout(function () {
+            iframe.style.width = '200px';
+            iframe.style.height = '200px';
+            iframe.contentDocument.body.getBoundingClientRect();
+            setTimeout(function () {
+                checkFlags('', {eventType: 'resize', scoped: true, relatedTargetScoped: false});
+                finishJSTest();
+            }, 0);
+        }, 0);
+    }
+
+    document.body.appendChild(iframe);
+}
+
+testLoadEvent();
+
+// FIXME: Test abort and loadedmetadata events.
+
+jsTestIsAsync = true;
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index 6a2cb40..b5b2d41 100644 (file)
@@ -48,6 +48,9 @@ cancelBubble : 'false'
 cancelable : 'false'
 clipboardData : 'undefined'
 currentTarget : '[object XMLHttpRequest]'
+deepPath : 'function deepPath() {
+    [native code]
+}'
 defaultPrevented : 'false'
 eventPhase : '2'
 initEvent : 'function initEvent() {
@@ -60,7 +63,9 @@ position : '174'
 preventDefault : 'function preventDefault() {
     [native code]
 }'
+relatedTargetScoped : 'false'
 returnValue : 'true'
+scoped : 'true'
 srcElement : '[object XMLHttpRequest]'
 stopImmediatePropagation : 'function stopImmediatePropagation() {
     [native code]
index 5020351..312b66a 100644 (file)
@@ -28,6 +28,7 @@ cancelable: true,
 clipboardData: undefined,
 colno: 14,
 currentTarget: [object Worker],
+deepPath: function deepPath() { [native code] },
 defaultPrevented: false,
 eventPhase: 2,
 filename: http://127.0.0.1:8000/workers/resources/worker-importScripts-error.js,
@@ -36,7 +37,9 @@ isTrusted: true,
 lineno: 2,
 message: Error: Script error.,
 preventDefault: function preventDefault() { [native code] },
+relatedTargetScoped: false,
 returnValue: true,
+scoped: true,
 srcElement: [object Worker],
 stopImmediatePropagation: function stopImmediatePropagation() { [native code] },
 stopPropagation: function stopPropagation() { [native code] },
index dacdb29..c68ecee 100644 (file)
@@ -48,6 +48,8 @@ DISPLAYABLE PROPERTIES:
     eventPhase
     bubbles
     cancelable
+    scoped
+    relatedTargetScoped
     timeStamp
     defaultPrevented
     srcElement
@@ -65,12 +67,15 @@ ALL PROPERTIES:
     eventPhase
     bubbles
     cancelable
+    scoped
+    relatedTargetScoped
     timeStamp
     defaultPrevented
     srcElement
     returnValue
     cancelBubble
     clipboardData
+    deepPath
     stopPropagation
     preventDefault
     initEvent
diff --git a/LayoutTests/platform/ios-simulator/fast/shadow-dom/trusted-event-scoped-flags-expected.txt b/LayoutTests/platform/ios-simulator/fast/shadow-dom/trusted-event-scoped-flags-expected.txt
new file mode 100644 (file)
index 0000000..1015845
--- /dev/null
@@ -0,0 +1,67 @@
+
+world
+PASS input.type = "radio"; log(input, "change"); input.click(); eventType is "change"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS log(form, "reset"); form.reset(); eventType is "reset"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS form.focus(); log(input, "focus"); input.focus(); eventType is "focus"
+PASS scoped is false
+PASS relatedTargetScoped is true
+
+PASS log(input, "blur"); form.focus(); eventType is "blur"
+PASS scoped is false
+PASS relatedTargetScoped is true
+
+FAIL input.type = "text"; log(input, "mousemove"); eventSender.mouseMoveTo(x, y); eventType should be mousemove (of type string). Was undefined (of type undefined).
+FAIL scoped should be false (of type boolean). Was undefined (of type undefined).
+FAIL relatedTargetScoped should be false (of type boolean). Was undefined (of type undefined).
+
+FAIL log(input, "mousedown"); eventSender.mouseDown(); eventType should be mousedown (of type string). Was undefined (of type undefined).
+FAIL scoped should be false (of type boolean). Was undefined (of type undefined).
+FAIL relatedTargetScoped should be false (of type boolean). Was undefined (of type undefined).
+
+FAIL log(input, "mouseup"); eventSender.mouseUp(); eventType should be mouseup (of type string). Was undefined (of type undefined).
+FAIL scoped should be false (of type boolean). Was undefined (of type undefined).
+FAIL relatedTargetScoped should be false (of type boolean). Was undefined (of type undefined).
+
+FAIL log(input, "mouseout"); eventSender.mouseMoveTo(0, 0); eventType should be mouseout (of type string). Was undefined (of type undefined).
+FAIL scoped should be false (of type boolean). Was undefined (of type undefined).
+FAIL relatedTargetScoped should be true (of type boolean). Was undefined (of type undefined).
+
+FAIL log(input, "mouseover"); eventSender.mouseMoveTo(x, y); eventType should be mouseover (of type string). Was undefined (of type undefined).
+FAIL scoped should be false (of type boolean). Was undefined (of type undefined).
+FAIL relatedTargetScoped should be true (of type boolean). Was undefined (of type undefined).
+
+input.value = "hello"; eventSender.mouseMoveTo(input.offsetLeft + 1, y); eventSender.mouseDown();
+FAIL log(input, "select"); eventSender.mouseMoveTo(input.offsetLeft + input.offsetWidth - 2, y); eventSender.mouseUp(); eventType should be select (of type string). Was undefined (of type undefined).
+FAIL scoped should be true (of type boolean). Was undefined (of type undefined).
+FAIL relatedTargetScoped should be false (of type boolean). Was undefined (of type undefined).
+
+FAIL log(editableElement, "selectstart"); eventSender.mouseMoveTo(editableElement.offsetLeft + 1, y); eventSender.mouseDown(); eventType should be selectstart (of type string). Was undefined (of type undefined).
+FAIL scoped should be true (of type boolean). Was undefined (of type undefined).
+FAIL relatedTargetScoped should be false (of type boolean). Was undefined (of type undefined).
+
+PASS eventType is "load"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS eventType is "error"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS eventType is "scroll"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS eventType is "resize"
+PASS scoped is true
+PASS relatedTargetScoped is false
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
index 2bf6228..21f5671 100644 (file)
@@ -1,3 +1,60 @@
+2016-03-11  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Add Event.deepPath() and Event.scoped
+        https://bugs.webkit.org/show_bug.cgi?id=153538
+        <rdar://problem/24363836>
+
+        Reviewed by Darin Adler.
+
+        Added the support for deepPath(), scoped, and relatedTargetScoped on Event.prototype for shadow DOM:
+        http://w3c.github.io/webcomponents/spec/shadow/#extensions-to-event-interface
+        and updated the EventPath class to respect scoped and relatedTargetScoped flags as specified at:
+        http://w3c.github.io/webcomponents/spec/shadow/#get-the-parent
+
+        Tests: fast/shadow-dom/Extensions-to-Event-Interface.html
+               fast/shadow-dom/trusted-event-scoped-flags.html
+
+        * bindings/scripts/CodeGeneratorJS.pm:
+        (GenerateConstructorDefinition): Added the support for Conditional for InitializedByEventConstructor.
+        * bindings/scripts/test/GObject/WebKitDOMTestEventConstructor.cpp:
+        * bindings/scripts/test/GObject/WebKitDOMTestEventConstructor.h:
+        * bindings/scripts/test/JS/JSTestEventConstructor.cpp:
+        * bindings/scripts/test/ObjC/DOMTestEventConstructor.h:
+        * bindings/scripts/test/ObjC/DOMTestEventConstructor.mm:
+        * bindings/scripts/test/TestEventConstructor.idl: Added a test case for using InitializedByEventConstructor
+        with Conditional.
+        * dom/Event.cpp:
+        (WebCore::Event::Event): Initialize m_scoped and m_relatedTargetScoped from EventInit dictionary.
+        (WebCore::Event::scoped): Added. Implements http://w3c.github.io/webcomponents/spec/shadow/#scoped-flag
+        (WebCore::Event::deepPath): Added.
+        * dom/Event.h:
+        (WebCore::Event::relatedTargetScoped): Added. Overridden by FocusEvent and MouseEvent to implement
+        http://w3c.github.io/webcomponents/spec/shadow/#relatedtargetscoped-flag
+        (WebCore::Event::setEventPath): Added.
+        (WebCore::Event::clearEventPath): Added.
+        * dom/Event.idl: Added scoped, relatedTargetScoped, and deepPath() conditionally enabled for shadow DOM.
+        * dom/EventContext.h:
+        (WebCore::EventContext::currentTarget):
+        * dom/EventDispatcher.cpp:
+        (WebCore::EventDispatcher::dispatchEvent): Set the event path while the event is being dispatched.
+        * dom/EventPath.cpp:
+        (WebCore::shouldEventCrossShadowBoundary): Check event.scoped flag instead of hard-coding a list of events here
+        which has been moved to Event::scoped. See above.
+        (WebCore::EventPath::setRelatedTarget): Check m_event.relatedTargetScoped() instead of hard-coding a list of
+        events here. relatedTargetScoped is overridden by FocusEvent and MouseEvent.
+        (WebCore::EventPath::hasEventListeners): Fixed the misleading variable name.
+        (WebCore::isUnclosedNodeOf): Added. Implements http://w3c.github.io/webcomponents/spec/shadow/#dfn-unclosed-node
+        (WebCore::EventPath::computePathDisclosedToTarget): Added. Implements the algorithm to filter event targets:
+        http://w3c.github.io/webcomponents/spec/shadow/#widl-Event-deepPath-sequence-EventTarget
+        * dom/EventPath.h:
+        * dom/FocusEvent.cpp:
+        (WebCore::FocusEvent::relatedTargetScoped): Returns true when this is a trusted event per:
+        http://w3c.github.io/webcomponents/spec/shadow/#relatedtargetscoped-flag
+        * dom/FocusEvent.h:
+        * dom/MouseEvent.cpp:
+        (WebCore::MouseEvent::relatedTargetScoped): Ditto.
+        * dom/MouseEvent.h:
+
 2016-03-11  John Wilander  <wilander@apple.com>
 
         Move prevalent resource classifier from WebCore to WebKit.
index 72d13fd..607d80d 100644 (file)
@@ -4856,10 +4856,16 @@ END
                 if ($attribute->signature->extendedAttributes->{"InitializedByEventConstructor"}) {
                     my $attributeName = $attribute->signature->name;
                     my $attributeImplName = $attribute->signature->extendedAttributes->{"ImplementedAs"} || $attributeName;
+                    my $conditionalString = $codeGenerator->GenerateConditionalString($attribute->signature);
+
+                    push(@implContent, "#if ${conditionalString}\n") if $conditionalString;
+
                     push(@implContent, <<END);
     if (!dictionary.tryGetProperty("${attributeName}", eventInit.${attributeImplName}))
         return false;
 END
+                    push(@implContent, "#endif\n") if $conditionalString;
+
                 }
             }
 
index 0682424..462e62a 100644 (file)
@@ -71,6 +71,7 @@ enum {
     PROP_0,
     PROP_ATTR1,
     PROP_ATTR2,
+    PROP_ATTR3,
 };
 
 static void webkit_dom_test_event_constructor_finalize(GObject* object)
@@ -94,6 +95,9 @@ static void webkit_dom_test_event_constructor_get_property(GObject* object, guin
     case PROP_ATTR2:
         g_value_take_string(value, webkit_dom_test_event_constructor_get_attr2(self));
         break;
+    case PROP_ATTR3:
+        g_value_take_string(value, webkit_dom_test_event_constructor_get_attr3(self));
+        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propertyId, pspec);
         break;
@@ -139,6 +143,16 @@ static void webkit_dom_test_event_constructor_class_init(WebKitDOMTestEventConst
             "",
             WEBKIT_PARAM_READABLE));
 
+    g_object_class_install_property(
+        gobjectClass,
+        PROP_ATTR3,
+        g_param_spec_string(
+            "attr3",
+            "TestEventConstructor:attr3",
+            "read-only gchar* TestEventConstructor:attr3",
+            "",
+            WEBKIT_PARAM_READABLE));
+
 }
 
 static void webkit_dom_test_event_constructor_init(WebKitDOMTestEventConstructor* request)
@@ -165,3 +179,18 @@ gchar* webkit_dom_test_event_constructor_get_attr2(WebKitDOMTestEventConstructor
     return result;
 }
 
+gchar* webkit_dom_test_event_constructor_get_attr3(WebKitDOMTestEventConstructor* self)
+{
+#if ENABLE(SPECIAL_EVENT)
+    WebCore::JSMainThreadNullState state;
+    g_return_val_if_fail(WEBKIT_DOM_IS_TEST_EVENT_CONSTRUCTOR(self), 0);
+    WebCore::TestEventConstructor* item = WebKit::core(self);
+    gchar* result = convertToUTF8String(item->attr3());
+    return result;
+#else
+    UNUSED_PARAM(self);
+    WEBKIT_WARN_FEATURE_NOT_PRESENT("Special Event")
+    return 0;
+#endif /* ENABLE(SPECIAL_EVENT) */
+}
+
index 663fbce..cda20d4 100644 (file)
@@ -69,6 +69,17 @@ webkit_dom_test_event_constructor_get_attr1(WebKitDOMTestEventConstructor* self)
 WEBKIT_API gchar*
 webkit_dom_test_event_constructor_get_attr2(WebKitDOMTestEventConstructor* self);
 
+/**
+ * webkit_dom_test_event_constructor_get_attr3:
+ * @self: A #WebKitDOMTestEventConstructor
+ *
+ * Returns: A #gchar
+ *
+ * Stability: Unstable
+**/
+WEBKIT_API gchar*
+webkit_dom_test_event_constructor_get_attr3(WebKitDOMTestEventConstructor* self);
+
 G_END_DECLS
 
 #endif /* WEBKIT_DOM_USE_UNSTABLE_API */
index 5da3c2f..e2af6d0 100644 (file)
@@ -38,6 +38,9 @@ namespace WebCore {
 
 JSC::EncodedJSValue jsTestEventConstructorAttr1(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
 JSC::EncodedJSValue jsTestEventConstructorAttr2(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
+#if ENABLE(SPECIAL_EVENT)
+JSC::EncodedJSValue jsTestEventConstructorAttr3(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
+#endif
 JSC::EncodedJSValue jsTestEventConstructorConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::PropertyName);
 bool setJSTestEventConstructorConstructor(JSC::ExecState*, JSC::EncodedJSValue, JSC::EncodedJSValue);
 
@@ -106,6 +109,10 @@ bool fillTestEventConstructorInit(TestEventConstructorInit& eventInit, JSDiction
 {
     if (!dictionary.tryGetProperty("attr2", eventInit.attr2))
         return false;
+#if ENABLE(SPECIAL_EVENT)
+    if (!dictionary.tryGetProperty("attr3", eventInit.attr3))
+        return false;
+#endif
     return true;
 }
 
@@ -131,6 +138,11 @@ static const HashTableValue JSTestEventConstructorPrototypeTableValues[] =
     { "constructor", DontEnum, NoIntrinsic, { (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsTestEventConstructorConstructor), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(setJSTestEventConstructorConstructor) } },
     { "attr1", ReadOnly | CustomAccessor, NoIntrinsic, { (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsTestEventConstructorAttr1), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } },
     { "attr2", ReadOnly | CustomAccessor, NoIntrinsic, { (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsTestEventConstructorAttr2), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } },
+#if ENABLE(SPECIAL_EVENT)
+    { "attr3", ReadOnly | CustomAccessor, NoIntrinsic, { (intptr_t)static_cast<PropertySlot::GetValueFunc>(jsTestEventConstructorAttr3), (intptr_t) static_cast<PutPropertySlot::PutValueFunc>(0) } },
+#else
+    { 0, 0, NoIntrinsic, { 0, 0 } },
+#endif
 };
 
 const ClassInfo JSTestEventConstructorPrototype::s_info = { "TestEventConstructorPrototype", &Base::s_info, 0, CREATE_METHOD_TABLE(JSTestEventConstructorPrototype) };
@@ -194,6 +206,23 @@ EncodedJSValue jsTestEventConstructorAttr2(ExecState* state, EncodedJSValue this
 }
 
 
+#if ENABLE(SPECIAL_EVENT)
+EncodedJSValue jsTestEventConstructorAttr3(ExecState* state, EncodedJSValue thisValue, PropertyName)
+{
+    UNUSED_PARAM(state);
+    UNUSED_PARAM(thisValue);
+    JSValue decodedThisValue = JSValue::decode(thisValue);
+    auto* castedThis = jsDynamicCast<JSTestEventConstructor*>(decodedThisValue);
+    if (UNLIKELY(!castedThis)) {
+        return throwGetterTypeError(*state, "TestEventConstructor", "attr3");
+    }
+    auto& impl = castedThis->wrapped();
+    JSValue result = jsStringWithCache(state, impl.attr3());
+    return JSValue::encode(result);
+}
+
+#endif
+
 EncodedJSValue jsTestEventConstructorConstructor(ExecState* state, EncodedJSValue thisValue, PropertyName)
 {
     JSTestEventConstructorPrototype* domObject = jsDynamicCast<JSTestEventConstructorPrototype*>(JSValue::decode(thisValue));
index 769e3c6..c1bfea4 100644 (file)
@@ -32,4 +32,5 @@ WEBKIT_CLASS_AVAILABLE_MAC(9876_5)
 WEBCORE_EXPORT @interface DOMTestEventConstructor : DOMObject
 @property (readonly, copy) NSString *attr1;
 @property (readonly, copy) NSString *attr2;
+@property (readonly, copy) NSString *attr3;
 @end
index bf9bf3d..3b9a188 100644 (file)
     return IMPL->attr2();
 }
 
+#if ENABLE(SPECIAL_EVENT)
+- (NSString *)attr3
+{
+    WebCore::JSMainThreadNullState state;
+    return IMPL->attr3();
+}
+#endif
+
 @end
 
 WebCore::TestEventConstructor* core(DOMTestEventConstructor *wrapper)
index a69eb8d..0444076 100644 (file)
@@ -34,4 +34,5 @@
     // Attributes
     readonly attribute DOMString attr1;
     [InitializedByEventConstructor] readonly attribute DOMString attr2;
+    [InitializedByEventConstructor, Conditional=SPECIAL_EVENT] readonly attribute DOMString attr3;
 };
index a6328ff..e87cf1a 100644 (file)
@@ -23,6 +23,7 @@
 #include "config.h"
 #include "Event.h"
 
+#include "EventPath.h"
 #include "EventTarget.h"
 #include "UserGestureIndicator.h"
 #include <wtf/CurrentTime.h>
@@ -59,6 +60,8 @@ Event::Event(const AtomicString& eventType, const EventInit& initializer)
     , m_type(eventType)
     , m_canBubble(initializer.bubbles)
     , m_cancelable(initializer.cancelable)
+    , m_scoped(initializer.scoped)
+    , m_relatedTargetScoped(initializer.relatedTargetScoped)
     , m_createTime(convertSecondsToDOMTimeStamp(currentTime()))
 {
 }
@@ -83,6 +86,26 @@ void Event::initEvent(const AtomicString& eventTypeArg, bool canBubbleArg, bool
     m_cancelable = cancelableArg;
 }
 
+bool Event::scoped() const
+{
+    if (m_scoped)
+        return true;
+
+    // http://w3c.github.io/webcomponents/spec/shadow/#scoped-flag
+    if (!isTrusted())
+        return false;
+
+    return m_type == eventNames().abortEvent
+        || m_type == eventNames().changeEvent
+        || m_type == eventNames().errorEvent
+        || m_type == eventNames().loadEvent
+        || m_type == eventNames().resetEvent
+        || m_type == eventNames().resizeEvent
+        || m_type == eventNames().scrollEvent
+        || m_type == eventNames().selectEvent
+        || m_type == eventNames().selectstartEvent;
+}
+
 EventInterface Event::eventInterface() const
 {
     return EventInterfaceType;
@@ -163,6 +186,13 @@ void Event::setTarget(RefPtr<EventTarget>&& target)
         receivedTarget();
 }
 
+Vector<EventTarget*> Event::deepPath() const
+{
+    if (!m_eventPath)
+        return Vector<EventTarget*>();
+    return m_eventPath->computePathDisclosedToTarget(*m_target);
+}
+
 void Event::receivedTarget()
 {
 }
index d44cae2..4ce9268 100644 (file)
 namespace WebCore {
 
 class DataTransfer;
+class EventPath;
 class EventTarget;
 class HTMLIFrameElement;
 
 struct EventInit {
     bool bubbles { false };
     bool cancelable { false };
+    bool scoped { false };
+    bool relatedTargetScoped { false };
 };
 
 enum EventInterface {
@@ -114,8 +117,15 @@ public:
 
     bool bubbles() const { return m_canBubble; }
     bool cancelable() const { return m_cancelable; }
+    bool scoped() const;
+    virtual bool relatedTargetScoped() const { return m_relatedTargetScoped; }
+
     DOMTimeStamp timeStamp() const { return m_createTime; }
 
+    void setEventPath(const EventPath& path) { m_eventPath = &path; }
+    void clearEventPath() { m_eventPath = nullptr; }
+    Vector<EventTarget*> deepPath() const;
+
     void stopPropagation() { m_propagationStopped = true; }
     void stopImmediatePropagation() { m_immediatePropagationStopped = true; }
 
@@ -198,6 +208,8 @@ private:
     AtomicString m_type;
     bool m_canBubble { false };
     bool m_cancelable { false };
+    bool m_scoped { false };
+    bool m_relatedTargetScoped { false };
 
     bool m_propagationStopped { false };
     bool m_immediatePropagationStopped { false };
@@ -208,6 +220,7 @@ private:
 
     unsigned short m_eventPhase { 0 };
     EventTarget* m_currentTarget { nullptr };
+    const EventPath* m_eventPath { nullptr };
     RefPtr<EventTarget> m_target;
     DOMTimeStamp m_createTime;
 
index 9f82bbc..e1c6bd5 100644 (file)
     readonly attribute unsigned short   eventPhase;
     [InitializedByEventConstructor] readonly attribute boolean bubbles;
     [InitializedByEventConstructor] readonly attribute boolean cancelable;
+    [InitializedByEventConstructor, Conditional=SHADOW_DOM, EnabledAtRuntime=ShadowDOM] readonly attribute boolean scoped;
+    [InitializedByEventConstructor, Conditional=SHADOW_DOM, EnabledAtRuntime=ShadowDOM] readonly attribute boolean relatedTargetScoped;
     readonly attribute DOMTimeStamp     timeStamp;
 
+    [Conditional=SHADOW_DOM, EnabledAtRuntime=ShadowDOM] sequence<Node> deepPath();
+
     void               stopPropagation();
     void               preventDefault();
     [ObjCLegacyUnnamedParameters] void initEvent([Default=Undefined] optional DOMString eventTypeArg, 
index 2ccc5ea..a8aedab 100644 (file)
@@ -47,6 +47,7 @@ public:
     virtual ~EventContext();
 
     Node* node() const { return m_node.get(); }
+    EventTarget* currentTarget() const { return m_currentTarget.get(); }
     EventTarget* target() const { return m_target.get(); }
     bool currentTargetSameAsTarget() const { return m_currentTarget.get() == m_target.get(); }
     virtual void handleLocalEvents(Event&) const;
index 585632c..ddd769a 100644 (file)
@@ -177,8 +177,11 @@ bool EventDispatcher::dispatchEvent(Node* origin, Event& event)
     if (is<HTMLInputElement>(*node))
         downcast<HTMLInputElement>(*node).willDispatchEvent(event, clickHandlingState);
 
-    if (!event.propagationStopped() && !eventPath.isEmpty())
+    if (!event.propagationStopped() && !eventPath.isEmpty()) {
+        event.setEventPath(eventPath);
         dispatchEventInDOM(event, eventPath, windowEventContext);
+        event.clearEventPath();
+    }
 
     event.setTarget(EventPath::eventTargetRespectingTargetRules(*node));
     event.setCurrentTarget(nullptr);
index 9abbb70..51b9f0e 100644 (file)
@@ -49,21 +49,8 @@ static inline bool shouldEventCrossShadowBoundary(Event& event, ShadowRoot& shad
     }
 #endif
 
-    // WebKit never allowed selectstart event to cross the the shadow DOM boundary.
-    // Changing this breaks existing sites.
-    // See https://bugs.webkit.org/show_bug.cgi?id=52195 for details.
-    const AtomicString& eventType = event.type();
     bool targetIsInShadowRoot = targetNode && &targetNode->treeScope().rootNode() == &shadowRoot;
-    return !targetIsInShadowRoot
-        || !(eventType == eventNames().abortEvent
-        || eventType == eventNames().changeEvent
-        || eventType == eventNames().errorEvent
-        || eventType == eventNames().loadEvent
-        || eventType == eventNames().resetEvent
-        || eventType == eventNames().resizeEvent
-        || eventType == eventNames().scrollEvent
-        || eventType == eventNames().selectEvent
-        || eventType == eventNames().selectstartEvent);
+    return !targetIsInShadowRoot || !event.scoped();
 }
 
 static Node* nodeOrHostIfPseudoElement(Node* node)
@@ -174,10 +161,7 @@ void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget)
     RelatedNodeRetargeter retargeter(*relatedNode, downcast<MouseOrFocusEventContext>(*m_path[0]).node()->treeScope());
 
     bool originIsRelatedTarget = &origin == relatedNode;
-    // FIXME: We should add a new flag on Event instead.
-    bool shouldTrimEventPath = m_event.type() == eventNames().mouseoverEvent
-        || m_event.type() == eventNames().mousemoveEvent
-        || m_event.type() == eventNames().mouseoutEvent;
+    bool relatedTargetScoped = m_event.relatedTargetScoped();
     Node& rootNodeInOriginTreeScope = origin.treeScope().rootNode();
     TreeScope* previousTreeScope = nullptr;
     size_t originalEventPathSize = m_path.size();
@@ -189,14 +173,14 @@ void EventPath::setRelatedTarget(Node& origin, EventTarget& relatedTarget)
             retargeter.moveToNewTreeScope(previousTreeScope, currentTreeScope);
 
         Node* currentRelatedNode = retargeter.currentNode(currentTreeScope);
-        if (UNLIKELY(shouldTrimEventPath && !originIsRelatedTarget && context.target() == currentRelatedNode)) {
+        if (UNLIKELY(relatedTargetScoped && !originIsRelatedTarget && context.target() == currentRelatedNode)) {
             m_path.shrink(contextIndex);
             break;
         }
 
         context.setRelatedTarget(currentRelatedNode);
 
-        if (UNLIKELY(shouldTrimEventPath && originIsRelatedTarget && context.node() == &rootNodeInOriginTreeScope)) {
+        if (UNLIKELY(relatedTargetScoped && originIsRelatedTarget && context.node() == &rootNodeInOriginTreeScope)) {
             m_path.shrink(contextIndex + 1);
             break;
         }
@@ -251,14 +235,53 @@ void EventPath::retargetTouchLists(const TouchEvent& touchEvent)
 
 bool EventPath::hasEventListeners(const AtomicString& eventType) const
 {
-    for (auto& eventPath : m_path) {
-        if (eventPath->node()->hasEventListeners(eventType))
+    for (auto& context : m_path) {
+        if (context->node()->hasEventListeners(eventType))
             return true;
     }
 
     return false;
 }
 
+// http://w3c.github.io/webcomponents/spec/shadow/#dfn-unclosed-node
+static bool isUnclosedNodeOf(const Node& a, const Node& b)
+{
+    // Use Vector instead of HashSet since we expect the number of ancestor tree scopes to be small.
+    Vector<TreeScope*, 8> treeScopesOpenToB;
+
+    for (auto* scope = &b.treeScope(); scope; scope = scope->parentTreeScope())
+        treeScopesOpenToB.append(scope);
+
+    for (auto* treeScopeThatCanAccessA = &a.treeScope(); treeScopeThatCanAccessA; treeScopeThatCanAccessA = treeScopeThatCanAccessA->parentTreeScope()) {
+        for (auto* openToB : treeScopesOpenToB) {
+            if (openToB == treeScopeThatCanAccessA)
+                return true;
+        }
+        auto& root = treeScopeThatCanAccessA->rootNode();
+        if (is<ShadowRoot>(root) && downcast<ShadowRoot>(root).type() != ShadowRoot::Type::Open)
+            break;
+    }
+
+    return false;
+}
+
+Vector<EventTarget*> EventPath::computePathDisclosedToTarget(const EventTarget& target) const
+{
+    Vector<EventTarget*> path;
+    const Node* targetNode = const_cast<EventTarget&>(target).toNode();
+    if (!targetNode)
+        return path;
+
+    for (auto& context : m_path) {
+        if (Node* nodeInPath = context->currentTarget()->toNode()) {
+            if (isUnclosedNodeOf(*nodeInPath, *targetNode))
+                path.append(context->currentTarget());
+        }
+    }
+
+    return path;
+}
+
 RelatedNodeRetargeter::RelatedNodeRetargeter(Node& relatedNode, TreeScope& targetTreeScope)
     : m_relatedNode(relatedNode)
     , m_retargetedRelatedNode(&relatedNode)
index 529e644..a977324 100644 (file)
@@ -48,6 +48,8 @@ public:
 
     EventContext* lastContextIfExists() { return m_path.isEmpty() ? nullptr : m_path.last().get(); }
 
+    Vector<EventTarget*> computePathDisclosedToTarget(const EventTarget&) const;
+
     static EventTarget* eventTargetRespectingTargetRules(Node& referenceNode)
     {
         if (is<PseudoElement>(referenceNode))
index 37c0756..33023b6 100644 (file)
@@ -52,4 +52,9 @@ FocusEvent::FocusEvent(const AtomicString& type, const FocusEventInit& initializ
 {
 }
 
+bool FocusEvent::relatedTargetScoped() const
+{
+    return (isTrusted() && m_relatedTarget) || UIEvent::relatedTargetScoped();
+}
+
 } // namespace WebCore
index 5df487e..0327036 100644 (file)
@@ -58,6 +58,8 @@ private:
     FocusEvent(const AtomicString& type, bool canBubble, bool cancelable, AbstractView*, int, RefPtr<EventTarget>&&);
     FocusEvent(const AtomicString& type, const FocusEventInit&);
 
+    bool relatedTargetScoped() const override;
+
     bool isFocusEvent() const override;
 
     RefPtr<EventTarget> m_relatedTarget;
index 9944fa6..1824f6f 100644 (file)
@@ -189,6 +189,11 @@ bool MouseEvent::canTriggerActivationBehavior(const Event& event)
     return event.type() == eventNames().clickEvent && (!is<MouseEvent>(event) || downcast<MouseEvent>(event).button() != RightButton);
 }
 
+bool MouseEvent::relatedTargetScoped() const
+{
+    return (isTrusted() && m_relatedTarget) || UIEvent::relatedTargetScoped();
+}
+
 int MouseEvent::which() const
 {
     // For the DOM, the return values for left, middle and right mouse buttons are 0, 1, 2, respectively.
index 3a4f963..c2e6b70 100644 (file)
@@ -121,6 +121,8 @@ protected:
 
     MouseEvent();
 
+    bool relatedTargetScoped() const override;
+
 private:
     unsigned short m_button;
     bool m_buttonDown;