+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
--- /dev/null
+
+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
+
--- /dev/null
+<!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>
<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)
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
<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>
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) {
--- /dev/null
+
+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;
+}
--- /dev/null
+
+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
+
--- /dev/null
+<!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>
cancelable : 'false'
clipboardData : 'undefined'
currentTarget : '[object XMLHttpRequest]'
+deepPath : 'function deepPath() {
+ [native code]
+}'
defaultPrevented : 'false'
eventPhase : '2'
initEvent : 'function initEvent() {
preventDefault : 'function preventDefault() {
[native code]
}'
+relatedTargetScoped : 'false'
returnValue : 'true'
+scoped : 'true'
srcElement : '[object XMLHttpRequest]'
stopImmediatePropagation : 'function stopImmediatePropagation() {
[native code]
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,
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] },
eventPhase
bubbles
cancelable
+ scoped
+ relatedTargetScoped
timeStamp
defaultPrevented
srcElement
eventPhase
bubbles
cancelable
+ scoped
+ relatedTargetScoped
timeStamp
defaultPrevented
srcElement
returnValue
cancelBubble
clipboardData
+ deepPath
stopPropagation
preventDefault
initEvent
--- /dev/null
+
+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
+
+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.
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;
+
}
}
PROP_0,
PROP_ATTR1,
PROP_ATTR2,
+ PROP_ATTR3,
};
static void webkit_dom_test_event_constructor_finalize(GObject* object)
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;
"",
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)
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) */
+}
+
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 */
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);
{
if (!dictionary.tryGetProperty("attr2", eventInit.attr2))
return false;
+#if ENABLE(SPECIAL_EVENT)
+ if (!dictionary.tryGetProperty("attr3", eventInit.attr3))
+ return false;
+#endif
return true;
}
{ "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) };
}
+#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));
WEBCORE_EXPORT @interface DOMTestEventConstructor : DOMObject
@property (readonly, copy) NSString *attr1;
@property (readonly, copy) NSString *attr2;
+@property (readonly, copy) NSString *attr3;
@end
return IMPL->attr2();
}
+#if ENABLE(SPECIAL_EVENT)
+- (NSString *)attr3
+{
+ WebCore::JSMainThreadNullState state;
+ return IMPL->attr3();
+}
+#endif
+
@end
WebCore::TestEventConstructor* core(DOMTestEventConstructor *wrapper)
// Attributes
readonly attribute DOMString attr1;
[InitializedByEventConstructor] readonly attribute DOMString attr2;
+ [InitializedByEventConstructor, Conditional=SPECIAL_EVENT] readonly attribute DOMString attr3;
};
#include "config.h"
#include "Event.h"
+#include "EventPath.h"
#include "EventTarget.h"
#include "UserGestureIndicator.h"
#include <wtf/CurrentTime.h>
, m_type(eventType)
, m_canBubble(initializer.bubbles)
, m_cancelable(initializer.cancelable)
+ , m_scoped(initializer.scoped)
+ , m_relatedTargetScoped(initializer.relatedTargetScoped)
, m_createTime(convertSecondsToDOMTimeStamp(currentTime()))
{
}
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;
receivedTarget();
}
+Vector<EventTarget*> Event::deepPath() const
+{
+ if (!m_eventPath)
+ return Vector<EventTarget*>();
+ return m_eventPath->computePathDisclosedToTarget(*m_target);
+}
+
void Event::receivedTarget()
{
}
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 {
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; }
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 };
unsigned short m_eventPhase { 0 };
EventTarget* m_currentTarget { nullptr };
+ const EventPath* m_eventPath { nullptr };
RefPtr<EventTarget> m_target;
DOMTimeStamp m_createTime;
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,
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;
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);
}
#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)
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();
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;
}
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)
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))
{
}
+bool FocusEvent::relatedTargetScoped() const
+{
+ return (isTrusted() && m_relatedTarget) || UIEvent::relatedTargetScoped();
+}
+
} // namespace WebCore
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;
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.
MouseEvent();
+ bool relatedTargetScoped() const override;
+
private:
unsigned short m_button;
bool m_buttonDown;