Diff
Modified: trunk/LayoutTests/ChangeLog (92921 => 92922)
--- trunk/LayoutTests/ChangeLog 2011-08-12 05:13:39 UTC (rev 92921)
+++ trunk/LayoutTests/ChangeLog 2011-08-12 05:18:28 UTC (rev 92922)
@@ -1,3 +1,22 @@
+2011-08-11 Hayato Ito <[email protected]>
+
+ Implement proper handling of events with a related target in regard to shadow DOM boundaries.
+ https://bugs.webkit.org/show_bug.cgi?id=65899
+
+ Reviewed by Dimitri Glazkov.
+
+ Fixes issues in the following corner cases:
+ 1. When both a target node and a relatedTarget node are immediate children of
+ the same shadow root, an event is not dispatched.
+ 2. If a target node is an ancestor of a relatedTarget node, crossing
+ shadow boundaries, or vice verse, an event is not dispatched or wrongly
+ dispatched.
+
+ * fast/dom/shadow/shadow-boundary-crossing-expected.txt: Renamed from LayoutTests/fast/events/shadow-boundary-crossing-expected.txt.
+ * fast/dom/shadow/shadow-boundary-crossing.html: Renamed from LayoutTests/fast/events/shadow-boundary-crossing.html.
+ * fast/dom/shadow/shadow-boundary-events-expected.txt: Added.
+ * fast/dom/shadow/shadow-boundary-events.html: Added.
+
2011-08-11 Ryosuke Niwa <[email protected]>
Share code between isStyleSpanOrSpanWithOnlyStyleAttribute, isUnstyledStyleSpan,
Copied: trunk/LayoutTests/fast/dom/shadow/shadow-boundary-crossing-expected.txt (from rev 92914, trunk/LayoutTests/fast/events/shadow-boundary-crossing-expected.txt) (0 => 92922)
--- trunk/LayoutTests/fast/dom/shadow/shadow-boundary-crossing-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/dom/shadow/shadow-boundary-crossing-expected.txt 2011-08-12 05:18:28 UTC (rev 92922)
@@ -0,0 +1,15 @@
+Tests to ensure that shadow DOM boundary is not crossed during event propagation. Can only run within DRT.
+
+See bug 46015 for details.
+
+Mutation events should not propagate out of the shadow DOM: PASS
+The selectstart event should not propagate out of the shadow DOM: PASS
+The mouseover/mouseout event between two elements inside the same shadow subtree should not propagate out of the shadow DOM: PASS
+The mouseover event in a shadow subtree, where related target is the tree host should not escape out of shadow DOM: PASS
+Events with relatedTarget should not escape out of shadow subtree when its host is the target: PASS
+The mouseover/mouseout event on a shadow subtree host should propagate out of the shadow DOM: PASS
+Label should look beyond shadow boundary to detect if it encloses its associated element: PASS
+Event's relatedTarget should be retargeted: PASS
+Other events should be retargeted: PASS
+After event dispatch, the event object should not reveal shadow DOM: PASS
+Focusing same shadow DOM element repeatedly should not trigger multiple focus/blur events: PASS
Property changes: trunk/LayoutTests/fast/dom/shadow/shadow-boundary-crossing-expected.txt
Added: svn:eol-style
Copied: trunk/LayoutTests/fast/dom/shadow/shadow-boundary-crossing.html (from rev 92914, trunk/LayoutTests/fast/events/shadow-boundary-crossing.html) (0 => 92922)
--- trunk/LayoutTests/fast/dom/shadow/shadow-boundary-crossing.html (rev 0)
+++ trunk/LayoutTests/fast/dom/shadow/shadow-boundary-crossing.html 2011-08-12 05:18:28 UTC (rev 92922)
@@ -0,0 +1,287 @@
+<html>
+<head>
+<script>
+
+var logDiv;
+
+function log(msg, success)
+{
+ logDiv.appendChild(document.createElement('div')).textContent = msg + ': ' + (success ? 'PASS' : 'FAIL');
+}
+
+function moveOver(element)
+{
+ if (!window.eventSender)
+ return;
+
+ var x = element.offsetLeft + element.offsetWidth / 2;
+ var y = element.offsetTop + element.offsetHeight / 2;
+ eventSender.mouseMoveTo(x, y);
+}
+
+function moveOverLeftQuarterOf(element)
+{
+ if (!window.eventSender)
+ return;
+
+ var x = element.offsetLeft + element.offsetWidth / 4;
+ var y = element.offsetTop + element.offsetHeight / 2;
+ eventSender.mouseMoveTo(x, y);
+}
+
+function moveOverRightQuarterOf(element)
+{
+ if (!window.eventSender)
+ return;
+
+ var x = element.offsetLeft + element.offsetWidth * 3 / 4;
+ var y = element.offsetTop + element.offsetHeight / 2;
+ eventSender.mouseMoveTo(x, y);
+}
+
+function clickOn(element)
+{
+ moveOver(element);
+ eventSender.mouseDown();
+ eventSender.mouseUp();
+}
+
+function clickOnLeftQuarterOf(element)
+{
+ if (!window.eventSender)
+ return;
+
+ moveOverLeftQuarterOf(element);
+ eventSender.mouseDown();
+ eventSender.mouseUp();
+}
+
+function leapForward()
+{
+ if (!window.eventSender)
+ return;
+
+ eventSender.leapForward(1000);
+}
+
+var tests = {
+ mutationEventPropagation: function()
+ {
+ var textarea = document.body.appendChild(document.createElement('textarea'));
+ var mutationEventFired;
+ textarea.addEventListener('DOMSubtreeModified', function(e)
+ {
+ mutationEventFired = true;
+ }, false);
+ textarea.value = 'test';
+ // Trigger style recalc and sadly, the actual mutation of the textarea shadow DOM.
+ textarea.offsetHeight;
+ log('Mutation events should not propagate out of the shadow DOM', !mutationEventFired);
+ textarea.parentNode.removeChild(textarea);
+ },
+ selectstartEventPropagation: function()
+ {
+ var textInput = document.body.appendChild(document.createElement('input'));
+ var selectstartEventFired = false;
+ document.selectstart = function()
+ {
+ selectstartEventFired = true;
+ }
+ clickOn(textInput);
+ log('The selectstart event should not propagate out of the shadow DOM', !selectstartEventFired);
+ textInput.parentNode.removeChild(textInput);
+ document.selectstart = null;
+ },
+ mouseOverAndOutPropagation: function()
+ {
+ var count = 0;
+ var fileInput = document.body.appendChild(document.createElement('input'));
+ fileInput.setAttribute('type', 'file');
+ var countEventDispatch = function()
+ {
+ count++;
+ }
+ moveOverLeftQuarterOf(fileInput);
+
+ document.body.addEventListener('mouseover', countEventDispatch, false);
+ document.body.addEventListener('mouseout', countEventDispatch, false);
+
+ moveOverRightQuarterOf(fileInput);
+
+ log("The mouseover/mouseout event between two elements inside the same shadow subtree should not propagate out of the shadow DOM", count == 0);
+
+ document.body.removeEventListener('mouseover', countEventDispatch, false);
+ document.body.removeEventListener('mouseout', countEventDispatch, false);
+ fileInput.parentNode.removeChild(fileInput);
+ },
+ relatedTargetAsHost: function()
+ {
+ var count = 0;
+ var relatedTarget = document.createElement('div');
+ relatedTarget.style.cssText = 'width: 50px; height: 50px; padding-left: 50px;';
+ document.body.appendChild(relatedTarget);
+ var target = document.createElement('div');
+ target.style.cssText = 'width: 50px; height: 50px';
+ internals.ensureShadowRoot(relatedTarget).appendChild(target);
+ moveOverLeftQuarterOf(relatedTarget);
+ var countEventDispatch = function()
+ {
+ count++;
+ }
+ relatedTarget.addEventListener('mouseover', countEventDispatch, false)
+ moveOverRightQuarterOf(relatedTarget);
+
+ log("The mouseover event in a shadow subtree, where related target is the tree host should not escape out of shadow DOM", count == 0);
+
+ relatedTarget.removeEventListener('mouseover', countEventDispatch, false);
+ document.body.removeChild(relatedTarget);
+ },
+ targetAsHost: function()
+ {
+ var count = 0;
+ var target = document.createElement('div');
+ target.style.cssText = 'width: 50px; height: 50px; padding-left: 50px;';
+ document.body.appendChild(target);
+ var relatedTarget = document.createElement('div');
+ relatedTarget.style.cssText = 'width: 50px; height: 50px';
+ internals.ensureShadowRoot(target).appendChild(relatedTarget);
+ moveOverRightQuarterOf(target);
+ var countEventDispatch = function(evt)
+ {
+ count++;
+ }
+ target.addEventListener('mouseover', countEventDispatch, false)
+ moveOverLeftQuarterOf(target);
+
+ log("Events with relatedTarget should not escape out of shadow subtree when its host is the target", count == 0);
+
+ target.removeEventListener('mouseout', countEventDispatch, false);
+ document.body.removeChild(target);
+ },
+ mouseOverOnHost: function()
+ {
+ var count = 0;
+ var input = document.body.appendChild(document.createElement('input'));
+ var countEventDispatch = function()
+ {
+ count++;
+ }
+
+ moveOver(document.body);
+ input.addEventListener('mouseover', countEventDispatch, false);
+ moveOver(input);
+
+ log("The mouseover/mouseout event on a shadow subtree host should propagate out of the shadow DOM", count == 1);
+
+ document.body.removeEventListener('mouseover', countEventDispatch, false);
+ input.parentNode.removeChild(input);
+ },
+ labelSyntheticClick: function()
+ {
+ var count = 0;
+ var label = document.body.appendChild(document.createElement('label'));
+ var searchInput = label.appendChild(document.createElement('input'));
+ searchInput.setAttribute('type', 'search');
+ searchInput.setAttribute('id', 'baz');
+ label.setAttribute('for', 'baz');
+ searchInput.addEventListener('click', function(e)
+ {
+ count++;
+ }, false);
+ clickOn(searchInput);
+ log("Label should look beyond shadow boundary to detect if it encloses its associated element", count == 1);
+ label.parentNode.removeChild(label);
+ },
+ /* This subtest started crashing after r89007:
+ * https://bugs.webkit.org/show_bug.cgi?id=62788
+ * I'm disabling this test for now while I ask for help understanding the problem.
+ defaultEventRetargeting: function()
+ {
+ var count = 0;
+ var fileInput = document.body.appendChild(document.createElement('input'));
+ fileInput.setAttribute('type', 'file');
+ var counter = function()
+ {
+ count++;
+ }
+ document.body.addEventListener('DOMActivate', counter, false);
+ clickOnLeftQuarterOf(fileInput);
+ log("Events for default event handler should not be retargeted", count == 1);
+ document.body.removeEventListener('DOMActivate', counter, false);
+ fileInput.parentNode.removeChild(fileInput);
+ },
+ */
+ relatedTargetRetargeting: function()
+ {
+ var count = 0;
+ var textInput = document.body.appendChild(document.createElement('input'));
+ var counter = function(evt)
+ {
+ if (evt.relatedTarget && !evt.relatedTarget.parentNode)
+ count++;
+ }
+ moveOver(textInput);
+ document.body.addEventListener("mouseover", counter, false);
+ moveOver(document.body);
+ document.body.removeEventListener("mouseover", counter, false);
+ log("Event's relatedTarget should be retargeted", count == 0);
+ textInput.parentNode.removeChild(textInput);
+ },
+ eventInProgress: function()
+ {
+ var textInput = document.body.appendChild(document.createElement('input'));
+ textInput.addEventListener('click', function(e)
+ {
+ log('Other events should be retargeted', e.target == textInput);
+ }, false);
+ clickOn(textInput);
+ textInput.parentNode.removeChild(textInput);
+ },
+ finalEventObject: function()
+ {
+ var textInput = document.body.appendChild(document.createElement('input'));
+ var storedEvent;
+ textInput.addEventListener('click', function(e)
+ {
+ storedEvent = e;
+ }, false);
+ clickOn(textInput);
+ log('After event dispatch, the event object should not reveal shadow DOM', storedEvent && storedEvent.target == textInput);
+ textInput.parentNode.removeChild(textInput);
+ },
+ focusEventPropagation: function()
+ {
+ var searchInput = document.body.appendChild(document.createElement('input'));
+ searchInput.setAttribute('type', 'search');
+ var count = 0;
+ searchInput.addEventListener('focus', function(evt)
+ {
+ count++;
+ });
+ clickOn(searchInput);
+ leapForward();
+ clickOn(searchInput);
+ log('Focusing same shadow DOM element repeatedly should not trigger multiple focus/blur events', count == 1);
+ searchInput.parentNode.removeChild(searchInput);
+ }
+};
+
+function runTest()
+{
+ if (window.layoutTestController)
+ layoutTestController.dumpAsText();
+
+ logDiv = document.getElementById('log');
+ for(var testName in tests) {
+ tests[testName]();
+ }
+}
+
+</script>
+</head>
+<body _onload_="runTest()">
+ <p>Tests to ensure that shadow DOM boundary is not crossed during event propagation. Can only run within DRT.
+ <p>See <a href="" 46015</a> for details.
+ <div id="log"></div>
+</body>
+</html>
Property changes: trunk/LayoutTests/fast/dom/shadow/shadow-boundary-crossing.html
Added: svn:eol-style
Added: trunk/LayoutTests/fast/dom/shadow/shadow-boundary-events-expected.txt (0 => 92922)
--- trunk/LayoutTests/fast/dom/shadow/shadow-boundary-events-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/dom/shadow/shadow-boundary-events-expected.txt 2011-08-12 05:18:28 UTC (rev 92922)
@@ -0,0 +1,64 @@
+Tests to ensure that shadow DOM boundary is not crossed during event propagation. Can only run within DRT.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+Move mouse from a node to its sibling node. All nodes are outside of shadow boundary.
+Moving mouse from divB to divC
+PASS dispatchedEvent("mouseover") is ["divC(<-divB)(@divC)", "divC(<-divB)(@divA)"]
+PASS dispatchedEvent("mouseout") is ["divB(<-divC)(@divB)", "divB(<-divC)(@divA)"]
+
+Target is an ancestor of relatedTarget. All nodes are outside of shadow boundary.
+Moving mouse from divB to divA
+PASS dispatchedEvent("mouseover") is ["divA(<-divB)(@divA)"]
+PASS dispatchedEvent("mouseout") is ["divB(<-divA)(@divB)", "divB(<-divA)(@divA)"]
+
+RelatedTarget is an ancestor of target. All nodes are outside of shadow boundary.
+Moving mouse from divA to divB
+PASS dispatchedEvent("mouseover") is ["divB(<-divA)(@divB)", "divB(<-divA)(@divA)"]
+PASS dispatchedEvent("mouseout") is ["divA(<-divB)(@divA)"]
+
+Both target and relatedTarget are immediate children of the same shadow root.
+Moving mouse from shadowD/shadowF/shadowG/divH to shadowD/shadowF/shadowG/divI
+PASS dispatchedEvent("mouseover") is ["divI(<-divH)(@divI)"]
+PASS dispatchedEvent("mouseout") is ["divH(<-divI)(@divH)"]
+
+Target is an ancestor of relatedTarget.
+Moving mouse from shadowD/shadowF/shadowG/divI to shadowD/divE
+PASS dispatchedEvent("mouseover") is ["divE(<-shadowF)(@divE)"]
+PASS dispatchedEvent("mouseout") is ["divI(<-divE)(@divI)", "shadowG(<-divE)(@shadowG)", "shadowF(<-divE)(@shadowF)", "shadowF(<-divE)(@divE)"]
+
+Target (shadow host) is an ancestor of relatedTarget.
+Moving mouse from shadowD/shadowF/shadowG/divI to shadowD/shadowF
+PASS dispatchedEvent("mouseover") is []
+PASS dispatchedEvent("mouseout") is ["divI(<-shadowF)(@divI)", "shadowG(<-shadowF)(@shadowG)"]
+
+Target (shadow host) is an ancestor of relatedTarget (shadow host).
+Moving mouse from shadowD/shadowF/shadowG to shadowD
+PASS dispatchedEvent("mouseover") is []
+PASS dispatchedEvent("mouseout") is ["shadowG(<-shadowD)(@shadowG)", "shadowF(<-shadowD)(@shadowF)", "shadowF(<-shadowD)(@divE)"]
+
+RelatedTarget is ancestor of target.
+Moving mouse from shadowD/divE to shadowD/shadowF/shadowG/divI
+PASS dispatchedEvent("mouseover") is ["divI(<-divE)(@divI)", "shadowG(<-divE)(@shadowG)", "shadowF(<-divE)(@shadowF)", "shadowF(<-divE)(@divE)"]
+PASS dispatchedEvent("mouseout") is ["divE(<-shadowF)(@divE)"]
+
+RelatedTarget (shadow host) is ancestor of target.
+Moving mouse from shadowD/shadowF to shadowD/shadowF/shadowG/divI
+PASS dispatchedEvent("mouseover") is ["divI(<-shadowF)(@divI)", "shadowG(<-shadowF)(@shadowG)"]
+PASS dispatchedEvent("mouseout") is []
+
+RelatedTarget (shadow host) is an ancestor of target (shadow host).
+Moving mouse from shadowD to shadowD/shadowF/shadowG
+PASS dispatchedEvent("mouseover") is ["shadowG(<-shadowD)(@shadowG)", "shadowF(<-shadowD)(@shadowF)", "shadowF(<-shadowD)(@divE)"]
+PASS dispatchedEvent("mouseout") is []
+
+Target and relatedTarget exist in separated subtree, crossing shadow boundaries. Making sure that event is not dispatched beyond the lowest common boundary.
+Moving mouse from shadowD/shadowF/shadowG/divH to shadowD/shadowK/divL
+PASS dispatchedEvent("mouseover") is ["divL(<-shadowF)(@divL)", "shadowK(<-shadowF)(@shadowK)", "shadowK(<-shadowF)(@divJ)"]
+PASS dispatchedEvent("mouseout") is ["divH(<-shadowK)(@divH)", "shadowG(<-shadowK)(@shadowG)", "shadowF(<-shadowK)(@shadowF)", "shadowF(<-shadowK)(@divE)"]
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Property changes on: trunk/LayoutTests/fast/dom/shadow/shadow-boundary-events-expected.txt
___________________________________________________________________
Added: svn:eol-style
Added: trunk/LayoutTests/fast/dom/shadow/shadow-boundary-events.html (0 => 92922)
--- trunk/LayoutTests/fast/dom/shadow/shadow-boundary-events.html (rev 0)
+++ trunk/LayoutTests/fast/dom/shadow/shadow-boundary-events.html 2011-08-12 05:18:28 UTC (rev 92922)
@@ -0,0 +1,177 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<p id="description"></p>
+<div id="sandbox"></div>
+<pre id="console"></pre>
+<script>
+description("Tests to ensure that shadow DOM boundary is not crossed during event propagation. Can only run within DRT.");
+
+function moveMouseOver(element)
+{
+ if (!window.eventSender || !window.internals)
+ return;
+
+ var defaultPaddingSize = 20;
+ var x = element.offsetLeft + element.offsetWidth / 2;
+ var y;
+ if (element.hasChildNodes() || window.internals.shadowRoot(element))
+ y = element.offsetTop + defaultPaddingSize;
+ else
+ y = element.offsetTop + element.offsetHeight / 2;
+ eventSender.mouseMoveTo(x, y);
+}
+
+var eventRecords = {};
+
+function clearEventRecords()
+{
+ eventRecords = {};
+}
+
+function dispatchedEvent(eventType)
+{
+ var events = eventRecords[eventType];
+ if (!events)
+ return [];
+ return events;
+}
+
+function recordEvent(event)
+{
+ var eventType = event.type
+ if (!eventRecords[eventType]) {
+ eventRecords[eventType] = []
+ }
+ // Records each event in the following format per event type:
+ // eventRecords[eventType] = ['target.id(<-relatedTarget.id)(@currentTarget.id)',,,]
+ // * RelatedTarget and currentTarget may be omitted if they are not defined.
+ // A new event is pushed back to the array of its event type.
+ var eventString = '';
+ eventString += event.target.id;
+ if (event.relatedTarget)
+ eventString += '(<-' + event.relatedTarget.id + ')';
+ if (event.currentTarget)
+ eventString += '(@' + event.currentTarget.id + ')';
+ eventRecords[eventType].push(eventString);
+}
+
+function getElementInShadow(path)
+{
+ var ids = path.split('/');
+ var element = document.getElementById(ids[0]);
+ for (var i = 1; element != null && i < ids.length; ++i) {
+ var shadowRoot = internals.shadowRoot(element);
+ element = internals.getElementByIdInShadowRoot(shadowRoot, ids[i]);
+ }
+ return element;
+}
+
+function prepareDomTree(parent)
+{
+ parent.appendChild(
+ createDom('div', {'id': 'divA', 'style': 'padding-top: 40px'},
+ createDom('div', {'id': 'divB', 'style': 'width: 40px; height: 40px'}),
+ createDom('div', {'id': 'divC', 'style': 'width: 40px; height: 40px'}),
+ createShadow('div', {'id': 'shadowD', 'style': 'padding-top: 40px'},
+ createDom('div', {'id': 'divE', 'style': 'padding-top: 40px'},
+ createShadow('div', {'id': 'shadowF', 'style': 'padding-top: 40px'},
+ createShadow('div', {'id': 'shadowG', 'style': 'padding-top: 40px'},
+ createDom('div', {'id': 'divH', 'style': 'width: 40px; height: 40px'}),
+ createDom('div', {'id': 'divI', 'style': 'width: 40px; height: 40px'})))),
+ createDom('div', {'id': 'divJ', 'style': 'padding-top: 40px'},
+ createShadow('div', {'id': 'shadowK', 'style': 'padding-top: 40px'},
+ createDom('div', {'id': 'divL', 'style': 'width: 40px; height: 40px'}))))));
+
+ var ids = ['divA', 'divB', 'divC',
+ 'shadowD', 'shadowD/divE', 'shadowD/shadowF', 'shadowD/shadowF/shadowG',
+ 'shadowD/shadowF/shadowG/divH', 'shadowD/shadowF/shadowG/divI',
+ 'shadowD/divJ', 'shadowD/shadowK', 'shadowD/shadowK/divL'];
+ for (var i = 0; i < ids.length; ++i) {
+ var element = getElementInShadow(ids[i]);
+ element.addEventListener('mouseover', recordEvent, false);
+ element.addEventListener('mouseout', recordEvent, false);
+ }
+}
+
+function moveMouse(oldElementId, newElementId, message)
+{
+ debug('\n' + message + '\n' + 'Moving mouse from ' + oldElementId + ' to ' + newElementId);
+ moveMouseOver(getElementInShadow(oldElementId));
+ clearEventRecords();
+ moveMouseOver(getElementInShadow(newElementId));
+}
+
+function test()
+{
+ if (window.layoutTestController)
+ layoutTestController.dumpAsText();
+ prepareDomTree(document.getElementById('sandbox'));
+
+ moveMouse('divB', 'divC',
+ 'Move mouse from a node to its sibling node. All nodes are outside of shadow boundary.');
+ shouldBe('dispatchedEvent("mouseover")', '["divC(<-divB)(@divC)", "divC(<-divB)(@divA)"]');
+ shouldBe('dispatchedEvent("mouseout")', '["divB(<-divC)(@divB)", "divB(<-divC)(@divA)"]');
+
+ moveMouse('divB', 'divA',
+ 'Target is an ancestor of relatedTarget. All nodes are outside of shadow boundary.');
+ shouldBe('dispatchedEvent("mouseover")', '["divA(<-divB)(@divA)"]');
+ shouldBe('dispatchedEvent("mouseout")', '["divB(<-divA)(@divB)", "divB(<-divA)(@divA)"]');
+
+ moveMouse('divA', 'divB',
+ 'RelatedTarget is an ancestor of target. All nodes are outside of shadow boundary.');
+ shouldBe('dispatchedEvent("mouseover")', '["divB(<-divA)(@divB)", "divB(<-divA)(@divA)"]');
+ shouldBe('dispatchedEvent("mouseout")', '["divA(<-divB)(@divA)"]');
+
+ moveMouse('shadowD/shadowF/shadowG/divH', 'shadowD/shadowF/shadowG/divI',
+ 'Both target and relatedTarget are immediate children of the same shadow root.');
+ shouldBe('dispatchedEvent("mouseover")', '["divI(<-divH)(@divI)"]');
+ shouldBe('dispatchedEvent("mouseout")', '["divH(<-divI)(@divH)"]');
+
+ moveMouse('shadowD/shadowF/shadowG/divI', 'shadowD/divE',
+ 'Target is an ancestor of relatedTarget.');
+ shouldBe('dispatchedEvent("mouseover")', '["divE(<-shadowF)(@divE)"]');
+ shouldBe('dispatchedEvent("mouseout")', '["divI(<-divE)(@divI)", "shadowG(<-divE)(@shadowG)", "shadowF(<-divE)(@shadowF)", "shadowF(<-divE)(@divE)"]');
+
+ moveMouse('shadowD/shadowF/shadowG/divI', 'shadowD/shadowF',
+ 'Target (shadow host) is an ancestor of relatedTarget.');
+ shouldBe('dispatchedEvent("mouseover")', '[]');
+ shouldBe('dispatchedEvent("mouseout")', '["divI(<-shadowF)(@divI)", "shadowG(<-shadowF)(@shadowG)"]');
+
+ moveMouse('shadowD/shadowF/shadowG', 'shadowD',
+ 'Target (shadow host) is an ancestor of relatedTarget (shadow host).');
+ shouldBe('dispatchedEvent("mouseover")', '[]');
+ shouldBe('dispatchedEvent("mouseout")', '["shadowG(<-shadowD)(@shadowG)", "shadowF(<-shadowD)(@shadowF)", "shadowF(<-shadowD)(@divE)"]');
+
+ moveMouse('shadowD/divE', 'shadowD/shadowF/shadowG/divI',
+ 'RelatedTarget is ancestor of target.');
+ shouldBe('dispatchedEvent("mouseover")', '["divI(<-divE)(@divI)", "shadowG(<-divE)(@shadowG)", "shadowF(<-divE)(@shadowF)", "shadowF(<-divE)(@divE)"]');
+ shouldBe('dispatchedEvent("mouseout")', '["divE(<-shadowF)(@divE)"]');
+
+ moveMouse('shadowD/shadowF', 'shadowD/shadowF/shadowG/divI',
+ 'RelatedTarget (shadow host) is ancestor of target.');
+ shouldBe('dispatchedEvent("mouseover")', '["divI(<-shadowF)(@divI)", "shadowG(<-shadowF)(@shadowG)"]');
+ shouldBe('dispatchedEvent("mouseout")', '[]');
+
+ moveMouse('shadowD', 'shadowD/shadowF/shadowG',
+ 'RelatedTarget (shadow host) is an ancestor of target (shadow host).');
+ shouldBe('dispatchedEvent("mouseover")', '["shadowG(<-shadowD)(@shadowG)", "shadowF(<-shadowD)(@shadowF)", "shadowF(<-shadowD)(@divE)"]');
+ shouldBe('dispatchedEvent("mouseout")', '[]');
+
+ moveMouse('shadowD/shadowF/shadowG/divH', 'shadowD/shadowK/divL',
+ 'Target and relatedTarget exist in separated subtree, crossing shadow boundaries. Making sure that event is not dispatched beyond the lowest common boundary.');
+ shouldBe('dispatchedEvent("mouseover")', '["divL(<-shadowF)(@divL)", "shadowK(<-shadowF)(@shadowK)", "shadowK(<-shadowF)(@divJ)"]');
+ shouldBe('dispatchedEvent("mouseout")', '["divH(<-shadowK)(@divH)", "shadowG(<-shadowK)(@shadowG)", "shadowF(<-shadowK)(@shadowF)", "shadowF(<-shadowK)(@divE)"]');
+}
+
+test();
+
+var successfullyParsed = true;
+</script>
+<script src=""
+</body>
+</html>
Property changes on: trunk/LayoutTests/fast/dom/shadow/shadow-boundary-events.html
___________________________________________________________________
Added: svn:eol-style
Deleted: trunk/LayoutTests/fast/events/shadow-boundary-crossing-expected.txt (92921 => 92922)
--- trunk/LayoutTests/fast/events/shadow-boundary-crossing-expected.txt 2011-08-12 05:13:39 UTC (rev 92921)
+++ trunk/LayoutTests/fast/events/shadow-boundary-crossing-expected.txt 2011-08-12 05:18:28 UTC (rev 92922)
@@ -1,15 +0,0 @@
-Tests to ensure that shadow DOM boundary is not crossed during event propagation. Can only run within DRT.
-
-See bug 46015 for details.
-
-Mutation events should not propagate out of the shadow DOM: PASS
-The selectstart event should not propagate out of the shadow DOM: PASS
-The mouseover/mouseout event between two elements inside the same shadow subtree should not propagate out of the shadow DOM: PASS
-The mouseover event in a shadow subtree, where related target is the tree host should not escape out of shadow DOM: PASS
-Events with relatedTarget should not escape out of shadow subtree when its host is the target: PASS
-The mouseover/mouseout event on a shadow subtree host should propagate out of the shadow DOM: PASS
-Label should look beyond shadow boundary to detect if it encloses its associated element: PASS
-Event's relatedTarget should be retargeted: PASS
-Other events should be retargeted: PASS
-After event dispatch, the event object should not reveal shadow DOM: PASS
-Focusing same shadow DOM element repeatedly should not trigger multiple focus/blur events: PASS
Deleted: trunk/LayoutTests/fast/events/shadow-boundary-crossing.html (92921 => 92922)
--- trunk/LayoutTests/fast/events/shadow-boundary-crossing.html 2011-08-12 05:13:39 UTC (rev 92921)
+++ trunk/LayoutTests/fast/events/shadow-boundary-crossing.html 2011-08-12 05:18:28 UTC (rev 92922)
@@ -1,287 +0,0 @@
-<html>
-<head>
-<script>
-
-var logDiv;
-
-function log(msg, success)
-{
- logDiv.appendChild(document.createElement('div')).textContent = msg + ': ' + (success ? 'PASS' : 'FAIL');
-}
-
-function moveOver(element)
-{
- if (!window.eventSender)
- return;
-
- var x = element.offsetLeft + element.offsetWidth / 2;
- var y = element.offsetTop + element.offsetHeight / 2;
- eventSender.mouseMoveTo(x, y);
-}
-
-function moveOverLeftQuarterOf(element)
-{
- if (!window.eventSender)
- return;
-
- var x = element.offsetLeft + element.offsetWidth / 4;
- var y = element.offsetTop + element.offsetHeight / 2;
- eventSender.mouseMoveTo(x, y);
-}
-
-function moveOverRightQuarterOf(element)
-{
- if (!window.eventSender)
- return;
-
- var x = element.offsetLeft + element.offsetWidth * 3 / 4;
- var y = element.offsetTop + element.offsetHeight / 2;
- eventSender.mouseMoveTo(x, y);
-}
-
-function clickOn(element)
-{
- moveOver(element);
- eventSender.mouseDown();
- eventSender.mouseUp();
-}
-
-function clickOnLeftQuarterOf(element)
-{
- if (!window.eventSender)
- return;
-
- moveOverLeftQuarterOf(element);
- eventSender.mouseDown();
- eventSender.mouseUp();
-}
-
-function leapForward()
-{
- if (!window.eventSender)
- return;
-
- eventSender.leapForward(1000);
-}
-
-var tests = {
- mutationEventPropagation: function()
- {
- var textarea = document.body.appendChild(document.createElement('textarea'));
- var mutationEventFired;
- textarea.addEventListener('DOMSubtreeModified', function(e)
- {
- mutationEventFired = true;
- }, false);
- textarea.value = 'test';
- // Trigger style recalc and sadly, the actual mutation of the textarea shadow DOM.
- textarea.offsetHeight;
- log('Mutation events should not propagate out of the shadow DOM', !mutationEventFired);
- textarea.parentNode.removeChild(textarea);
- },
- selectstartEventPropagation: function()
- {
- var textInput = document.body.appendChild(document.createElement('input'));
- var selectstartEventFired = false;
- document.selectstart = function()
- {
- selectstartEventFired = true;
- }
- clickOn(textInput);
- log('The selectstart event should not propagate out of the shadow DOM', !selectstartEventFired);
- textInput.parentNode.removeChild(textInput);
- document.selectstart = null;
- },
- mouseOverAndOutPropagation: function()
- {
- var count = 0;
- var fileInput = document.body.appendChild(document.createElement('input'));
- fileInput.setAttribute('type', 'file');
- var countEventDispatch = function()
- {
- count++;
- }
- moveOverLeftQuarterOf(fileInput);
-
- document.body.addEventListener('mouseover', countEventDispatch, false);
- document.body.addEventListener('mouseout', countEventDispatch, false);
-
- moveOverRightQuarterOf(fileInput);
-
- log("The mouseover/mouseout event between two elements inside the same shadow subtree should not propagate out of the shadow DOM", count == 0);
-
- document.body.removeEventListener('mouseover', countEventDispatch, false);
- document.body.removeEventListener('mouseout', countEventDispatch, false);
- fileInput.parentNode.removeChild(fileInput);
- },
- relatedTargetAsHost: function()
- {
- var count = 0;
- var relatedTarget = document.createElement('div');
- relatedTarget.style.cssText = 'width: 50px; height: 50px; padding-left: 50px;';
- document.body.appendChild(relatedTarget);
- var target = document.createElement('div');
- target.style.cssText = 'width: 50px; height: 50px';
- internals.ensureShadowRoot(relatedTarget).appendChild(target);
- moveOverLeftQuarterOf(relatedTarget);
- var countEventDispatch = function()
- {
- count++;
- }
- relatedTarget.addEventListener('mouseover', countEventDispatch, false)
- moveOverRightQuarterOf(relatedTarget);
-
- log("The mouseover event in a shadow subtree, where related target is the tree host should not escape out of shadow DOM", count == 0);
-
- relatedTarget.removeEventListener('mouseover', countEventDispatch, false);
- document.body.removeChild(relatedTarget);
- },
- targetAsHost: function()
- {
- var count = 0;
- var target = document.createElement('div');
- target.style.cssText = 'width: 50px; height: 50px; padding-left: 50px;';
- document.body.appendChild(target);
- var relatedTarget = document.createElement('div');
- relatedTarget.style.cssText = 'width: 50px; height: 50px';
- internals.ensureShadowRoot(target).appendChild(relatedTarget);
- moveOverRightQuarterOf(target);
- var countEventDispatch = function(evt)
- {
- count++;
- }
- target.addEventListener('mouseover', countEventDispatch, false)
- moveOverLeftQuarterOf(target);
-
- log("Events with relatedTarget should not escape out of shadow subtree when its host is the target", count == 0);
-
- target.removeEventListener('mouseout', countEventDispatch, false);
- document.body.removeChild(target);
- },
- mouseOverOnHost: function()
- {
- var count = 0;
- var input = document.body.appendChild(document.createElement('input'));
- var countEventDispatch = function()
- {
- count++;
- }
-
- moveOver(document.body);
- input.addEventListener('mouseover', countEventDispatch, false);
- moveOver(input);
-
- log("The mouseover/mouseout event on a shadow subtree host should propagate out of the shadow DOM", count == 1);
-
- document.body.removeEventListener('mouseover', countEventDispatch, false);
- input.parentNode.removeChild(input);
- },
- labelSyntheticClick: function()
- {
- var count = 0;
- var label = document.body.appendChild(document.createElement('label'));
- var searchInput = label.appendChild(document.createElement('input'));
- searchInput.setAttribute('type', 'search');
- searchInput.setAttribute('id', 'baz');
- label.setAttribute('for', 'baz');
- searchInput.addEventListener('click', function(e)
- {
- count++;
- }, false);
- clickOn(searchInput);
- log("Label should look beyond shadow boundary to detect if it encloses its associated element", count == 1);
- label.parentNode.removeChild(label);
- },
- /* This subtest started crashing after r89007:
- * https://bugs.webkit.org/show_bug.cgi?id=62788
- * I'm disabling this test for now while I ask for help understanding the problem.
- defaultEventRetargeting: function()
- {
- var count = 0;
- var fileInput = document.body.appendChild(document.createElement('input'));
- fileInput.setAttribute('type', 'file');
- var counter = function()
- {
- count++;
- }
- document.body.addEventListener('DOMActivate', counter, false);
- clickOnLeftQuarterOf(fileInput);
- log("Events for default event handler should not be retargeted", count == 1);
- document.body.removeEventListener('DOMActivate', counter, false);
- fileInput.parentNode.removeChild(fileInput);
- },
- */
- relatedTargetRetargeting: function()
- {
- var count = 0;
- var textInput = document.body.appendChild(document.createElement('input'));
- var counter = function(evt)
- {
- if (evt.relatedTarget && !evt.relatedTarget.parentNode)
- count++;
- }
- moveOver(textInput);
- document.body.addEventListener("mouseover", counter, false);
- moveOver(document.body);
- document.body.removeEventListener("mouseover", counter, false);
- log("Event's relatedTarget should be retargeted", count == 0);
- textInput.parentNode.removeChild(textInput);
- },
- eventInProgress: function()
- {
- var textInput = document.body.appendChild(document.createElement('input'));
- textInput.addEventListener('click', function(e)
- {
- log('Other events should be retargeted', e.target == textInput);
- }, false);
- clickOn(textInput);
- textInput.parentNode.removeChild(textInput);
- },
- finalEventObject: function()
- {
- var textInput = document.body.appendChild(document.createElement('input'));
- var storedEvent;
- textInput.addEventListener('click', function(e)
- {
- storedEvent = e;
- }, false);
- clickOn(textInput);
- log('After event dispatch, the event object should not reveal shadow DOM', storedEvent && storedEvent.target == textInput);
- textInput.parentNode.removeChild(textInput);
- },
- focusEventPropagation: function()
- {
- var searchInput = document.body.appendChild(document.createElement('input'));
- searchInput.setAttribute('type', 'search');
- var count = 0;
- searchInput.addEventListener('focus', function(evt)
- {
- count++;
- });
- clickOn(searchInput);
- leapForward();
- clickOn(searchInput);
- log('Focusing same shadow DOM element repeatedly should not trigger multiple focus/blur events', count == 1);
- searchInput.parentNode.removeChild(searchInput);
- }
-};
-
-function runTest()
-{
- if (window.layoutTestController)
- layoutTestController.dumpAsText();
-
- logDiv = document.getElementById('log');
- for(var testName in tests) {
- tests[testName]();
- }
-}
-
-</script>
-</head>
-<body _onload_="runTest()">
- <p>Tests to ensure that shadow DOM boundary is not crossed during event propagation. Can only run within DRT.
- <p>See <a href="" 46015</a> for details.
- <div id="log"></div>
-</body>
-</html>
Modified: trunk/Source/WebCore/ChangeLog (92921 => 92922)
--- trunk/Source/WebCore/ChangeLog 2011-08-12 05:13:39 UTC (rev 92921)
+++ trunk/Source/WebCore/ChangeLog 2011-08-12 05:18:28 UTC (rev 92922)
@@ -1,3 +1,22 @@
+2011-08-11 Hayato Ito <[email protected]>
+
+ Implement proper handling of events with a related target in regard to shadow DOM boundaries.
+ https://bugs.webkit.org/show_bug.cgi?id=65899
+
+ Reviewed by Dimitri Glazkov.
+
+ Fixes issues in the following corner cases:
+ 1. When both a target node and a relatedTarget node are immediate children of
+ the same shadow root, an event is not dispatched.
+ 2. If a target node is an ancestor of a relatedTarget node, crossing
+ shadow boundaries, or vice verse, an event is not dispatched or wrongly
+ dispatched.
+
+ Test: fast/dom/shadow/shadow-boundary-events.html
+
+ * dom/EventDispatcher.cpp:
+ (WebCore::EventDispatcher::adjustToShadowBoundaries):
+
2011-08-11 John Bauman <[email protected]>
Readback composited webgl results for printing
Modified: trunk/Source/WebCore/dom/EventDispatcher.cpp (92921 => 92922)
--- trunk/Source/WebCore/dom/EventDispatcher.cpp 2011-08-12 05:13:39 UTC (rev 92921)
+++ trunk/Source/WebCore/dom/EventDispatcher.cpp 2011-08-12 05:18:28 UTC (rev 92922)
@@ -164,20 +164,22 @@
if (!diverged) {
// The relatedTarget is an ancestor or shadowHost of the target.
// FIXME: Remove the first check once conversion to new shadow DOM is complete <http://webkit.org/b/48698>
- if (m_node->shadowHost() == relatedTarget.get() || isShadowHost(relatedTarget.get()))
- lowestCommonBoundary = m_ancestors.begin();
+ if (m_node->shadowHost() == relatedTarget.get() || isShadowHost(relatedTarget.get())) {
+ ASSERT(targetAncestor - 1 >= m_ancestors.begin());
+ lowestCommonBoundary = targetAncestor - 1;
+ }
} else if ((*firstDivergentBoundary) == m_node.get()) {
// Since ancestors does not contain target itself, we must account
// for the possibility that target is a shadowHost of relatedTarget
// and thus serves as the lowestCommonBoundary.
// Luckily, in this case the firstDivergentBoundary is target.
lowestCommonBoundary = m_ancestors.begin();
+ m_shouldPreventDispatch = true;
}
if (lowestCommonBoundary != m_ancestors.end()) {
// Trim ancestors to lowestCommonBoundary to keep events inside of the common shadow DOM subtree.
m_ancestors.shrink(lowestCommonBoundary - m_ancestors.begin());
- m_shouldPreventDispatch = !m_ancestors.size();
}
// Set event's related target to the first encountered shadow DOM boundary in the divergent subtree.
return firstDivergentBoundary != relatedTargetAncestors.begin() ? *firstDivergentBoundary : relatedTarget;