Title: [92922] trunk
Revision
92922
Author
[email protected]
Date
2011-08-11 22:18:28 -0700 (Thu, 11 Aug 2011)

Log Message

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.

Source/WebCore:

Test: fast/dom/shadow/shadow-boundary-events.html

* dom/EventDispatcher.cpp:
(WebCore::EventDispatcher::adjustToShadowBoundaries):

LayoutTests:

* 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.

Modified Paths

Added Paths

Removed Paths

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;
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to