Title: [235865] trunk
Revision
235865
Author
[email protected]
Date
2018-09-10 14:54:14 -0700 (Mon, 10 Sep 2018)

Log Message

mouseenter and mouseleave events don't get dispatched even when there is a capturing event listener for a slot ancestor
https://bugs.webkit.org/show_bug.cgi?id=188561

Reviewed by Darin Adler.

Source/WebCore:

This patch makes mouseenter and mouseleave events to work with shadow trees and slots therein, and makes them uncomposed
as discussed in https://github.com/w3c/uievents/issues/208.

This patch also makes these events dispatched on DOM tree ancestors of the currently hovered element instead of
render tree's hover ancestors to be consistent with the check in hierarchyHasCapturingEventListeners and other browsers.
In particular, using hover ancestors is problematic when there is an element with display: contents such as slot elements,
which do not have a render object.

Tests: fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary.html
       fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree.html
       fast/shadow-dom/mouseenter-mouseleave-on-slot-parent.html

* dom/MouseEvent.cpp:
(WebCore::MouseEvent::create):
* page/EventHandler.cpp:
(WebCore::nearestCommonHoverAncestor): Deleted.
(WebCore::hierarchyHasCapturingEventListeners): Use parentInComposedTree. Else we would miss capturing event listeners
on inclusive ancestors of slots.
(WebCore::EventHandler::updateMouseEventTargetNode): Use the composed tree's ancestor chain to fire mouseenter and
mouseleave events. This is needed to dispatch mouseenter / mouseleave events on slot elements. Also removed comments
which just state what is self-evident from the code beneath them.

LayoutTests:

Added tests for listening to mouseenter and mouseleave events using capturing and bubbling event listeners
across shadow boundaries.

* fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary-expected.txt: Added.
* fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary.html: Added.
* fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree-expected.txt: Added.
* fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree.html: Added.
* fast/shadow-dom/mouseenter-mouseleave-on-slot-parent-expected.txt: Added.
* fast/shadow-dom/mouseenter-mouseleave-on-slot-parent.html: Added.
* platform/ios/TestExpectations: Skip the new tests since mouse events aren't supported on iOS.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (235864 => 235865)


--- trunk/LayoutTests/ChangeLog	2018-09-10 21:48:55 UTC (rev 235864)
+++ trunk/LayoutTests/ChangeLog	2018-09-10 21:54:14 UTC (rev 235865)
@@ -1,3 +1,21 @@
+2018-09-07  Ryosuke Niwa  <[email protected]>
+
+        mouseenter and mouseleave events don't get dispatched even when there is a capturing event listener for a slot ancestor
+        https://bugs.webkit.org/show_bug.cgi?id=188561
+
+        Reviewed by Darin Adler.
+
+        Added tests for listening to mouseenter and mouseleave events using capturing and bubbling event listeners
+        across shadow boundaries.
+
+        * fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary-expected.txt: Added.
+        * fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary.html: Added.
+        * fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree-expected.txt: Added.
+        * fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree.html: Added.
+        * fast/shadow-dom/mouseenter-mouseleave-on-slot-parent-expected.txt: Added.
+        * fast/shadow-dom/mouseenter-mouseleave-on-slot-parent.html: Added.
+        * platform/ios/TestExpectations: Skip the new tests since mouse events aren't supported on iOS.
+
 2018-09-10  Daniel Bates  <[email protected]>
 
         [iOS] Arrow keys do not dispatch DOM events to non-editable elements

Added: trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary-expected.txt (0 => 235865)


--- trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary-expected.txt	2018-09-10 21:54:14 UTC (rev 235865)
@@ -0,0 +1,28 @@
+This tests mouseenter and mouseleave events across shadow boundaries.
+
+
+==Entering host parent==
+mouseenter on hostParent
+
+==Entering host==
+mouseenter on host
+
+==Entering shadow content==
+mouseenter on slotParent
+
+==Entering slotted text==
+mouseenter on slot
+
+==Entering slotted element==
+mouseenter on target
+mouseenter on targetParent
+
+==Leaving slotted element==
+mouseleave on target
+mouseleave on targetParent
+mouseleave on slot
+
+==Leaving host==
+mouseleave on slotParent
+mouseleave on host
+

Added: trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary.html (0 => 235865)


--- trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary.html	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary.html	2018-09-10 21:54:14 UTC (rev 235865)
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This tests mouseenter and mouseleave events across shadow boundaries.</p>
+<div id="hostParent" style="display: inline-block; border: solid 10px green;">
+<div id="host" style="display: inline-block;">
+    text content
+    <p id="targetParent" style="margin: 0;"><span id="target">element content</span></p>
+</div></div><pre id="log"></pre>
+<script>
+
+const host = document.getElementById('host');
+const shadowRoot = host.attachShadow({mode: 'closed'});
+shadowRoot.innerHTML = `
+    <div id="slotParent" style="width: 200px; margin: 10px; padding: 10px; border: solid 10px blue;">
+        <slot id="slot"></slot>
+    </div>`;
+const slot = shadowRoot.querySelector('slot');
+
+const target = document.getElementById('target');
+for (const element of [host.parentNode, host, target, target.parentNode, slot, slot.parentNode]) {
+    element.addEventListener('mouseenter', logEvent);
+    element.addEventListener('mouseleave', logEvent);
+}
+
+function log(text) {
+    document.getElementById('log').textContent += text + '\n';
+}
+
+function logEvent(event) {
+    log(`${event.type} on ${event.target.id}`);
+}
+
+function logPhase(phase) {
+    log(`\n==${phase}==`);
+}
+
+if (!window.eventSender)
+    document.write('This test requires eventSender');
+else {
+    testRunner.dumpAsText();
+    const hostRect = host.getBoundingClientRect();
+    const targetRect = target.getBoundingClientRect();
+    const xAboveText = hostRect.x + 40;
+    let y = hostRect.top;
+    logPhase('Entering host parent');
+    eventSender.mouseMoveTo(xAboveText, y - 5);
+    logPhase('Entering host');
+    eventSender.mouseMoveTo(xAboveText, y + 5);
+    logPhase('Entering shadow content');
+    eventSender.mouseMoveTo(xAboveText, y + 15);
+
+    logPhase('Entering slotted text');
+    eventSender.mouseMoveTo(xAboveText, targetRect.top - 5);
+    logPhase('Entering slotted element');
+    eventSender.mouseMoveTo(xAboveText, targetRect.top + 5);
+
+    logPhase('Leaving slotted element');
+    eventSender.mouseMoveTo(xAboveText, targetRect.bottom + 5);
+    logPhase('Leaving host');
+    eventSender.mouseMoveTo(xAboveText, hostRect.bottom + 5);
+    
+    host.parentNode.style.display = 'none';
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree-expected.txt (0 => 235865)


--- trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree-expected.txt	2018-09-10 21:54:14 UTC (rev 235865)
@@ -0,0 +1,17 @@
+This tests mouseenter and mouseleave events inside a shadow tree.
+To manually test, move the mouse cursor vertically across "hello, world" below.
+WebKit must generate mouseenter and mouseleve events exactly once on host (as well as hostParent, continer, and target).
+
+
+==Entering target==
+mouseenter on target
+mouseenter on container
+mouseenter on host
+mouseenter on hostParent
+
+==Leaving target==
+mouseleave on target
+mouseleave on container
+mouseleave on host
+mouseleave on hostParent
+

Added: trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree.html (0 => 235865)


--- trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree.html	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree.html	2018-09-10 21:54:14 UTC (rev 235865)
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This tests mouseenter and mouseleave events inside a shadow tree.<br>
+To manually test, move the mouse cursor vertically across "hello, world" below.<br>
+WebKit must generate mouseenter and mouseleve events exactly once on host (as well as hostParent, continer, and target).</p>
+<div id="hostParent"><div id="host"></div></div><pre id="log"></pre>
+<script>
+
+const host = document.getElementById('host');
+const shadowRoot = host.attachShadow({mode: 'closed'});
+shadowRoot.innerHTML = `
+    <div id="container">
+        <span id="target">hello, world</span>
+    </div>`;
+const container = shadowRoot.getElementById('container');
+const target = shadowRoot.getElementById('target');
+
+for (const element of [host.parentNode, host, container, target]) {
+    element.addEventListener('mouseenter', logEvent);
+    element.addEventListener('mouseleave', logEvent);
+}
+
+function log(text) {
+    document.getElementById('log').textContent += text + '\n';
+}
+
+function logEvent(event) {
+    if (event.composed)
+        log(`FAIL - ${event.type} on ${event.target.id} was composed`);
+    log(`${event.type} on ${event.target.id}`);
+}
+
+function logPhase(phase) {
+    log(`\n==${phase}==`);
+}
+
+if (!window.eventSender)
+    document.write('This test requires eventSender');
+else {
+    testRunner.dumpAsText();
+    const targetRect = target.getBoundingClientRect();
+    const x = targetRect.x + 5;
+    logPhase('Entering target');
+    eventSender.mouseMoveTo(x, targetRect.top + 5);
+    logPhase('Leaving target');
+    eventSender.mouseMoveTo(x, targetRect.bottom + 5);
+
+    host.parentNode.style.display = 'none';
+}
+
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-on-slot-parent-expected.txt (0 => 235865)


--- trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-on-slot-parent-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-on-slot-parent-expected.txt	2018-09-10 21:54:14 UTC (rev 235865)
@@ -0,0 +1,15 @@
+This tests mouseenter and mouseleave event fires when capturing event listeners are only present on the slot element's parent.
+To manually test, move the mouse cursor into the box below and move it out.
+You should see mouseenter events on slotContainer, slot, and target in that order and mouseleave events in the reverse order.
+
+
+==Entering target==
+mouseenter on target
+mouseenter on slot
+mouseenter on slotContainer
+
+==Leaving target==
+mouseleave on target
+mouseleave on slot
+mouseleave on slotContainer
+

Added: trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-on-slot-parent.html (0 => 235865)


--- trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-on-slot-parent.html	                        (rev 0)
+++ trunk/LayoutTests/fast/shadow-dom/mouseenter-mouseleave-on-slot-parent.html	2018-09-10 21:54:14 UTC (rev 235865)
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This tests mouseenter and mouseleave event fires when capturing event listeners are only present on the slot element's parent.<br>
+To manually test, move the mouse cursor into the box below and move it out.<br>
+You should see mouseenter events on slotContainer, slot, and target in that order and mouseleave events in the reverse order.</p>
+<div id="hostParent"><div id="host"><span id="target" style="border: solid 1px black; padding: 0.5rem;">content</span></div></div><pre id="log"></pre>
+<script>
+
+const host = document.getElementById('host');
+const shadowRoot = host.attachShadow({mode: 'closed'});
+shadowRoot.innerHTML = `
+    <div id="slotContainer">
+        <slot id="slot"></slot>
+    </div>`;
+const slot = shadowRoot.querySelector('slot');
+
+const target = document.getElementById('target');
+slot.parentNode.addEventListener('mouseenter', logEvent, true);
+slot.parentNode.addEventListener('mouseleave', logEvent, true);
+
+function log(text) {
+    document.getElementById('log').textContent += text + '\n';
+}
+
+function logEvent(event) {
+    log(`${event.type} on ${event.target.id}`);
+}
+
+function logPhase(phase) {
+    log(`\n==${phase}==`);
+}
+
+if (!window.eventSender)
+    document.write('This test requires eventSender');
+else {
+    testRunner.dumpAsText();
+    const targetRect = target.getBoundingClientRect();
+    const x = targetRect.x + 40;
+    logPhase('Entering target');
+    eventSender.mouseMoveTo(x, targetRect.top + 5);
+
+    logPhase('Leaving target');
+    eventSender.mouseMoveTo(x, targetRect.bottom + 5);
+    
+    host.parentNode.style.display = 'none';
+}
+
+</script>
+</body>
+</html>

Modified: trunk/LayoutTests/platform/ios/TestExpectations (235864 => 235865)


--- trunk/LayoutTests/platform/ios/TestExpectations	2018-09-10 21:48:55 UTC (rev 235864)
+++ trunk/LayoutTests/platform/ios/TestExpectations	2018-09-10 21:54:14 UTC (rev 235865)
@@ -683,6 +683,9 @@
 fast/selectors/not-active-hover-quirks.html [ Skip ]
 fast/selectors/not-active-hover-strict.html [ Skip ]
 fast/shapes/shape-outside-floats/shape-outside-clip-path-selection.html [ Skip ]
+fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary.html [ Skip ]
+fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree.html [ Skip ]
+fast/shadow-dom/mouseenter-mouseleave-on-slot-parent.html [ Skip ]
 fast/table/hittest-self-painting.html [ Skip ]
 fast/text/atsui-pointtooffset-calls-cg.html [ Skip ]
 fast/text/atsui-rtl-override-selection.html [ Skip ]

Modified: trunk/Source/WebCore/ChangeLog (235864 => 235865)


--- trunk/Source/WebCore/ChangeLog	2018-09-10 21:48:55 UTC (rev 235864)
+++ trunk/Source/WebCore/ChangeLog	2018-09-10 21:54:14 UTC (rev 235865)
@@ -1,3 +1,33 @@
+2018-09-07  Ryosuke Niwa  <[email protected]>
+
+        mouseenter and mouseleave events don't get dispatched even when there is a capturing event listener for a slot ancestor
+        https://bugs.webkit.org/show_bug.cgi?id=188561
+
+        Reviewed by Darin Adler.
+
+        This patch makes mouseenter and mouseleave events to work with shadow trees and slots therein, and makes them uncomposed
+        as discussed in https://github.com/w3c/uievents/issues/208.
+
+        This patch also makes these events dispatched on DOM tree ancestors of the currently hovered element instead of
+        render tree's hover ancestors to be consistent with the check in hierarchyHasCapturingEventListeners and other browsers.
+        In particular, using hover ancestors is problematic when there is an element with display: contents such as slot elements,
+        which do not have a render object.
+
+        Tests: fast/shadow-dom/mouseenter-mouseleave-across-shadow-boundary.html
+               fast/shadow-dom/mouseenter-mouseleave-inside-shadow-tree.html
+               fast/shadow-dom/mouseenter-mouseleave-on-slot-parent.html
+
+        * dom/MouseEvent.cpp:
+        (WebCore::MouseEvent::create):
+        * page/EventHandler.cpp:
+        (WebCore::nearestCommonHoverAncestor): Deleted.
+        (WebCore::hierarchyHasCapturingEventListeners): Use parentInComposedTree. Else we would miss capturing event listeners
+        on inclusive ancestors of slots.
+        (WebCore::EventHandler::updateMouseEventTargetNode): Use the composed tree's ancestor chain to fire mouseenter and
+        mouseleave events. This is needed to dispatch mouseenter / mouseleave events on slot elements. Also removed comments
+        which just state what is self-evident from the code beneath them.
+
+
 2018-09-08  Ryosuke Niwa  <[email protected]>
 
         Remove isOrphan check in ShadowRoot::setInnerHTML

Modified: trunk/Source/WebCore/dom/MouseEvent.cpp (235864 => 235865)


--- trunk/Source/WebCore/dom/MouseEvent.cpp	2018-09-10 21:48:55 UTC (rev 235864)
+++ trunk/Source/WebCore/dom/MouseEvent.cpp	2018-09-10 21:54:14 UTC (rev 235865)
@@ -49,8 +49,9 @@
     bool isMouseEnterOrLeave = eventType == eventNames().mouseenterEvent || eventType == eventNames().mouseleaveEvent;
     auto isCancelable = eventType != eventNames().mousemoveEvent && !isMouseEnterOrLeave ? IsCancelable::Yes : IsCancelable::No;
     auto canBubble = !isMouseEnterOrLeave ? CanBubble::Yes : CanBubble::No;
+    auto isComposed = !isMouseEnterOrLeave ? IsComposed::Yes : IsComposed::No;
 
-    return MouseEvent::create(eventType, canBubble, isCancelable, IsComposed::Yes, event.timestamp().approximateMonotonicTime(), WTFMove(view), detail,
+    return MouseEvent::create(eventType, canBubble, isCancelable, isComposed, event.timestamp().approximateMonotonicTime(), WTFMove(view), detail,
         event.globalPosition(), event.position(),
 #if ENABLE(POINTER_LOCK)
         event.movementDelta(),

Modified: trunk/Source/WebCore/page/EventHandler.cpp (235864 => 235865)


--- trunk/Source/WebCore/page/EventHandler.cpp	2018-09-10 21:48:55 UTC (rev 235864)
+++ trunk/Source/WebCore/page/EventHandler.cpp	2018-09-10 21:54:14 UTC (rev 235865)
@@ -33,6 +33,7 @@
 #include "CachedImage.h"
 #include "Chrome.h"
 #include "ChromeClient.h"
+#include "ComposedTreeAncestorIterator.h"
 #include "CursorList.h"
 #include "DocumentMarkerController.h"
 #include "DragController.h"
@@ -2444,24 +2445,9 @@
     return m_frame.document()->prepareMouseEvent(request, documentPointForWindowPoint(m_frame, mouseEvent.position()), mouseEvent);
 }
 
-static RenderElement* nearestCommonHoverAncestor(RenderElement* obj1, RenderElement* obj2)
-{
-    if (!obj1 || !obj2)
-        return nullptr;
-
-    for (RenderElement* currObj1 = obj1; currObj1; currObj1 = currObj1->hoverAncestor()) {
-        for (RenderElement* currObj2 = obj2; currObj2; currObj2 = currObj2->hoverAncestor()) {
-            if (currObj1 == currObj2)
-                return currObj1;
-        }
-    }
-
-    return nullptr;
-}
-
 static bool hierarchyHasCapturingEventListeners(Element* element, const AtomicString& eventName)
 {
-    for (ContainerNode* curr = element; curr; curr = curr->parentOrShadowHostNode()) {
+    for (ContainerNode* curr = element; curr; curr = curr->parentInComposedTree()) {
         if (curr->hasCapturingEventListeners(eventName))
             return true;
     }
@@ -2535,48 +2521,35 @@
             bool hasCapturingMouseEnterListener = hierarchyHasCapturingEventListeners(m_elementUnderMouse.get(), eventNames().mouseenterEvent);
             bool hasCapturingMouseLeaveListener = hierarchyHasCapturingEventListeners(m_lastElementUnderMouse.get(), eventNames().mouseleaveEvent);
 
-            RenderElement* oldHoverRenderer = m_lastElementUnderMouse ? m_lastElementUnderMouse->renderer() : nullptr;
-            RenderElement* newHoverRenderer = m_elementUnderMouse ? m_elementUnderMouse->renderer() : nullptr;
-            RenderElement* ancestor = nearestCommonHoverAncestor(oldHoverRenderer, newHoverRenderer);
-
             Vector<Ref<Element>, 32> leftElementsChain;
-            if (oldHoverRenderer) {
-                for (RenderElement* curr = oldHoverRenderer; curr && curr != ancestor; curr = curr->hoverAncestor()) {
-                    if (Element* element = curr->element())
-                        leftElementsChain.append(*element);
-                }
-            } else {
-                // If the old hovered element is not null but it's renderer is, it was probably detached.
-                // In this case, the old hovered element (and its ancestors) must be updated, to ensure it's normal style is re-applied.
-                for (Element* element = m_lastElementUnderMouse.get(); element; element = element->parentElement())
-                    leftElementsChain.append(*element);
-            }
+            for (Element* element = m_lastElementUnderMouse.get(); element; element = element->parentElementInComposedTree())
+                leftElementsChain.append(*element);
+            Vector<Ref<Element>, 32> enteredElementsChain;
+            for (Element* element = m_elementUnderMouse.get(); element; element = element->parentElementInComposedTree())
+                enteredElementsChain.append(*element);
 
-            Vector<Ref<Element>, 32> enteredElementsChain;
-            const Element* ancestorElement = ancestor ? ancestor->element() : nullptr;
-            for (RenderElement* curr = newHoverRenderer; curr; curr = curr->hoverAncestor()) {
-                if (Element *element = curr->element()) {
-                    if (element == ancestorElement)
+            if (!leftElementsChain.isEmpty() && !enteredElementsChain.isEmpty() && leftElementsChain.last().ptr() == enteredElementsChain.last().ptr()) {
+                size_t minHeight = std::min(leftElementsChain.size(), enteredElementsChain.size());
+                size_t i;
+                for (i = 0; i < minHeight; ++i) {
+                    if (leftElementsChain[leftElementsChain.size() - i - 1].ptr() != enteredElementsChain[enteredElementsChain.size() - i - 1].ptr())
                         break;
-                    enteredElementsChain.append(*element);
                 }
+                leftElementsChain.shrink(leftElementsChain.size() - i);
+                enteredElementsChain.shrink(enteredElementsChain.size() - i);
             }
 
-            // Send mouseout event to the old node.
             if (m_lastElementUnderMouse)
                 m_lastElementUnderMouse->dispatchMouseEvent(platformMouseEvent, eventNames().mouseoutEvent, 0, m_elementUnderMouse.get());
 
-            // Send mouseleave to the node hierarchy no longer under the mouse.
             for (auto& chain : leftElementsChain) {
                 if (hasCapturingMouseLeaveListener || chain->hasEventListeners(eventNames().mouseleaveEvent))
                     chain->dispatchMouseEvent(platformMouseEvent, eventNames().mouseleaveEvent, 0, m_elementUnderMouse.get());
             }
 
-            // Send mouseover event to the new node.
             if (m_elementUnderMouse)
                 m_elementUnderMouse->dispatchMouseEvent(platformMouseEvent, eventNames().mouseoverEvent, 0, m_lastElementUnderMouse.get());
 
-            // Send mouseleave event to the nodes hierarchy under the mouse.
             for (auto& chain : enteredElementsChain) {
                 if (hasCapturingMouseEnterListener || chain->hasEventListeners(eventNames().mouseenterEvent))
                     chain->dispatchMouseEvent(platformMouseEvent, eventNames().mouseenterEvent, 0, m_lastElementUnderMouse.get());
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to