Skip to content

Commit

Permalink
Capturing event listeners are called during bubbling phase for shadow…
Browse files Browse the repository at this point in the history
… hosts

https://bugs.webkit.org/show_bug.cgi?id=174288
LayoutTests/imported/w3c:


Reviewed by Darin Adler.

* web-platform-tests/dom/events/Event-dispatch-handlers-changed-expected.txt: Rebaselined. This test's
expectation is now wrong because event listner 3 is added after the event listener list is cloned for
capturing event listeners but before cloned for bubbling event listeners. As a result, event listener 3
is now invoked. It used to be not called because both bubbling and capturing event listeners are called
after cloning the event listner list once, which didn't have event listener 3.

* web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt: Rebaselined. This test expects
event listener 2, which is bubbling, to be called between two capturing event listeners 1 and 3, which
is no longer true after this patch.

Source/WebCore:

<rdar://problem/33530455>

Reviewed by Darin Adler.

Implemented the new behavior proposed in whatwg/dom#686 [1] to fix the problem
that capturing event listeners on a shadow host is invoked during bubbling phase when an event is
dispatched within its shadow tree.

To see why this is a problem, suppose we fire a composed event at span#target in the following DOM tree:
  section#hostParent
    + div#host -- ShadowRoot
                    - p#parent
                        - span#target
Then capturing and bubbling event listeners on #target, #parent, #host, and #hostParent are invoked in
the following order in WebKit & Chrome right now:

1. #hostParent, capturing, eventPhase: CAPTURING_PHASE
2. #parent, capturing, eventPhase: CAPTURING_PHASE
3. #target, capturing, eventPhase: AT_TARGET
4. #target, non-capturing, eventPhase: AT_TARGET
5. #parent, non-capturing, eventPhase: BUBBLING_PHASE
6. #host, capturing, eventPhase: AT_TARGET
7. #host, non-capturing, eventPhase: AT_TARGET
8. #hostParent, non-capturing, eventPhase: BUBBLING_PHASE

This is counter-intuitive because capturing event listeners on #host isn't invoked until bubblign phase
started. A more natural ordering would be:

1. #hostParent, capturing, eventPhase: CAPTURING_PHASE
2. #host, capturing, eventPhase: AT_TARGET
3. #parent, capturing, eventPhase: CAPTURING_PHASE
4. #target, capturing, eventPhase: AT_TARGET
5. #target, non-capturing, eventPhase: AT_TARGET
6. #parent, non-capturing, eventPhase: BUBBLING_PHASE
7. #host, non-capturing, eventPhase: AT_TARGET
8. #hostParent, non-capturing, eventPhase: BUBBLING_PHASE

This also happens to be the order by which Gecko's current shadow DOM implementation invoke event listners.
This patch implements this new behavior using the spec-change proposed in [1]. Note that this patch also
impacts the invocation order of event listeners when there is no shadow tree. Namely, before this patch,
event listeners on the event's target is invoked in the registration order. After this patch, all capturing
event listeners are invoked before bubbling event listeners are invoked.

To implement this behavior, this patch introduces EventTarget::EventInvokePhase indicating whether we're
in the capturing phase or bubbling phase to EventTarget::fireEventListeners. We can't use Event's eventPhase
enum because that's set to Event::Phase::AT_TARGET when we're at a shadow host.

Test: fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html

* Modules/modern-media-controls/media/media-controller-support.js:
(MediaControllerSupport.prototype.enable): Use capturing event listeners so that we can update the states of
media controls before author scripts recieve the event.
(MediaControllerSupport.prototype.disable): Ditto.
* dom/EventContext.cpp:
(WebCore::EventContext::handleLocalEvents const):
(WebCore::MouseOrFocusEventContext::handleLocalEvents const):
(WebCore::TouchEventContext::handleLocalEvents const):
* dom/EventContext.h:
* dom/EventDispatcher.cpp:
(WebCore::dispatchEventInDOM): Invoke capturing event listners even when target and current target are same.
This happens when the current target is a shadow host and event's target is in its shadow tree. Also merged
the special code path for the event's target with the code in the bubbling phase.
* dom/EventPath.cpp:
(WebCore::WindowEventContext::handleLocalEvents const):
* dom/EventTarget.cpp:
(WebCore::EventTarget::dispatchEvent): Invoke capturing and bubbling event listeners in the order.
(WebCore::EventTarget::fireEventListeners):
(WebCore::EventTarget::innerInvokeEventListeners): Renamed from fireEventListeners to match the spec. Use
EventInvokePhase to filter out event listeners so that we can invoke capturing event listners before bubbling
event listeners even when eventPhase is Event::Phase::AT_TARGET.
* dom/EventTarget.h:
* dom/Node.cpp:
(WebCore::Node::handleLocalEvents):
* dom/Node.h:
* html/HTMLFormElement.cpp:
(WebCore::HTMLFormElement::handleLocalEvents):
* html/HTMLFormElement.h:
* page/DOMWindow.cpp:
(WebCore::DOMWindow::dispatchEvent):

LayoutTests:


Reviewed by Darin Adler.

Added a W3C style testharness.js test and rebaselined two tests. See below for rationals of rebaselines.

* fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees-expected.txt: Added.
* fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html: Added.

* media/media-load-event-expected.txt: Rebaselined. The logging of oncanplaythrough event is now happening
before canplaythrough() is called because the logging is done by waitForEvent which uses a capturing event
listener whereas canplaythrough is called by a event handler, which is non-capturing.

* platform/ios-11/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt:
* platform/ios/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@236002 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
rniwa@webkit.org committed Sep 14, 2018
1 parent d3a818d commit 83a6595
Show file tree
Hide file tree
Showing 22 changed files with 373 additions and 51 deletions.
19 changes: 19 additions & 0 deletions LayoutTests/ChangeLog
@@ -1,3 +1,22 @@
2018-09-13 Ryosuke Niwa <rniwa@webkit.org>

Capturing event listeners are called during bubbling phase for shadow hosts
https://bugs.webkit.org/show_bug.cgi?id=174288

Reviewed by Darin Adler.

Added a W3C style testharness.js test and rebaselined two tests. See below for rationals of rebaselines.

* fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees-expected.txt: Added.
* fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html: Added.

* media/media-load-event-expected.txt: Rebaselined. The logging of oncanplaythrough event is now happening
before canplaythrough() is called because the logging is done by waitForEvent which uses a capturing event
listener whereas canplaythrough is called by a event handler, which is non-capturing.

* platform/ios-11/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt:
* platform/ios/imported/w3c/web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt:

2018-09-13 Justin Fan <justin_fan@apple.com>

Update webkit-webgl-test-harness.js for more details on WebGL 2 conformance tests part 4
Expand Down
@@ -0,0 +1,7 @@

PASS Capturing event listeners should be invoked before bubbling event listeners on the target without shadow trees
PASS Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a shadow tree
PASS Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a doubly nested shadow tree
PASS Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched via a slot
PASS Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a shadow tree which passes through another shadow tree

@@ -0,0 +1,191 @@
<!DOCTYPE html>
<html>
<head>
<title>Shadow DOM: Capturing event listeners should be invoked before bubbling event listeners</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../imported/w3c/web-platform-tests/shadow-dom/resources/shadow-dom.js"></script>
<script>

function attachEventListeners(eventType, tree) {
const eventLogs = [];
const makeComposedPathResult = (event) => event.composedPath().map((node) => node.id)
for (const id in tree) {
const node = tree[id];
node.addEventListener(eventType, event => eventLogs.push(
['bubbling', event.eventPhase, event.target.id, event.currentTarget.id, makeComposedPathResult(event)]), {capture: false});
node.addEventListener(eventType, event => eventLogs.push(
['capturing', event.eventPhase, event.target.id, event.currentTarget.id, makeComposedPathResult(event)]), {capture: true});
}
return eventLogs;
}

</script>
</head>
<body>

<div id="test1">
<div id="parent">
<div id="target"></div>
</div>
</div>
<script>
test(() => {
const tree = createTestTree(document.getElementById('test1'));
const logs = attachEventListeners('my-event', tree);
tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true }));

const composedPath = ['target', 'parent', 'test1'];
assert_object_equals(logs, [
['capturing', Event.CAPTURING_PHASE, 'target', 'test1', composedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'parent', composedPath],
['capturing', Event.AT_TARGET, 'target', 'target', composedPath],
['bubbling', Event.AT_TARGET, 'target', 'target', composedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'parent', composedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'test1', composedPath],
]);
}, 'Capturing event listeners should be invoked before bubbling event listeners on the target without shadow trees');
</script>

<div id="test2">
<div id="host">
<template id="shadowRoot" data-mode="closed">
<div id="target"></div>
</template>
</div>
</div>
<script>
test(() => {
const tree = createTestTree(document.getElementById('test2'));
const logs = attachEventListeners('my-event', tree);
tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true }));

const innerComposedPath = ['target', 'shadowRoot', 'host', 'test2'];
const outerComposedPath = ['host', 'test2'];
assert_object_equals(logs, [
['capturing', Event.CAPTURING_PHASE, 'host', 'test2', outerComposedPath],
['capturing', Event.AT_TARGET, 'host', 'host', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'shadowRoot', innerComposedPath],
['capturing', Event.AT_TARGET, 'target', 'target', innerComposedPath],
['bubbling', Event.AT_TARGET, 'target', 'target', innerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'shadowRoot', innerComposedPath],
['bubbling', Event.AT_TARGET, 'host', 'host', outerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'host', 'test2', outerComposedPath],
]);
}, 'Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a shadow tree');
</script>

<div id="test3">
<div id="outerHost">
<template id="outerShadowRoot" data-mode="closed">
<div id="innerHost">
<template id="innerShadowRoot" data-mode="closed">
<div id="target"></div>
</template>
</div>
</template>
</div>
</div>
<script>
test(() => {
const tree = createTestTree(document.getElementById('test3'));
const logs = attachEventListeners('my-event', tree);
tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true }));

const innerShadowComposedPath = ['target', 'innerShadowRoot', 'innerHost', 'outerShadowRoot', 'outerHost', 'test3'];
const outerShadowComposedPath = ['innerHost', 'outerShadowRoot', 'outerHost', 'test3'];
const outerComposedPath = ['outerHost', 'test3'];
assert_object_equals(logs, [
['capturing', Event.CAPTURING_PHASE, 'outerHost', 'test3', outerComposedPath],
['capturing', Event.AT_TARGET, 'outerHost', 'outerHost', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'innerHost', 'outerShadowRoot', outerShadowComposedPath],
['capturing', Event.AT_TARGET, 'innerHost', 'innerHost', outerShadowComposedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'innerShadowRoot', innerShadowComposedPath],
['capturing', Event.AT_TARGET, 'target', 'target', innerShadowComposedPath],

['bubbling', Event.AT_TARGET, 'target', 'target', innerShadowComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'innerShadowRoot', innerShadowComposedPath],
['bubbling', Event.AT_TARGET, 'innerHost', 'innerHost', outerShadowComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'innerHost', 'outerShadowRoot', outerShadowComposedPath],
['bubbling', Event.AT_TARGET, 'outerHost', 'outerHost', outerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'outerHost', 'test3', outerComposedPath],
]);
}, 'Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a doubly nested shadow tree');
</script>

<div id="test4">
<div id="host">
<template id="shadowRoot" data-mode="closed">
<slot id="slot"></slot>
</template>
<div id="target"></div>
</div>
</div>
<script>
test(() => {
const tree = createTestTree(document.getElementById('test4'));
const logs = attachEventListeners('my-event', tree);
tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true }));

const innerComposedPath = ['target', 'slot', 'shadowRoot', 'host', 'test4'];
const outerComposedPath = ['target', 'host', 'test4'];
assert_object_equals(logs, [
['capturing', Event.CAPTURING_PHASE, 'target', 'test4', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'host', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'shadowRoot', innerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'slot', innerComposedPath],
['capturing', Event.AT_TARGET, 'target', 'target', outerComposedPath],

['bubbling', Event.AT_TARGET, 'target', 'target', outerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'slot', innerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'shadowRoot', innerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'host', outerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'test4', outerComposedPath],
]);
}, 'Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched via a slot');
</script>

<div id="test5">
<div id="upperHost">
<template id="upperShadowRoot" data-mode="closed">
<slot id="upperSlot"></slot>
</template>
<div id="lowerHost">
<template id="lowerShadowRoot" data-mode="closed">
<div id="target"></div>
</template>
</div>
</div>
</div>
<script>
test(() => {
const tree = createTestTree(document.getElementById('test5'));
const logs = attachEventListeners('my-event', tree);
tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true }));

const lowerComposedPath = ['target', 'lowerShadowRoot', 'lowerHost', 'upperHost', 'test5'];
const upperComposedPath = ['lowerHost', 'upperSlot', 'upperShadowRoot', 'upperHost', 'test5'];
const outerComposedPath = ['lowerHost', 'upperHost', 'test5'];
assert_object_equals(logs, [
['capturing', Event.CAPTURING_PHASE, 'lowerHost', 'test5', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'lowerHost', 'upperHost', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'lowerHost', 'upperShadowRoot', upperComposedPath],
['capturing', Event.CAPTURING_PHASE, 'lowerHost', 'upperSlot', upperComposedPath],
['capturing', Event.AT_TARGET, 'lowerHost', 'lowerHost', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'lowerShadowRoot', lowerComposedPath],
['capturing', Event.AT_TARGET, 'target', 'target', lowerComposedPath],

['bubbling', Event.AT_TARGET, 'target', 'target', lowerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'lowerShadowRoot', lowerComposedPath],
['bubbling', Event.AT_TARGET, 'lowerHost', 'lowerHost', outerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'lowerHost', 'upperSlot', upperComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'lowerHost', 'upperShadowRoot', upperComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'lowerHost', 'upperHost', outerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'lowerHost', 'test5', outerComposedPath],
]);
}, 'Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a shadow tree which passes through another shadow tree');
</script>

</body>
</html>
17 changes: 17 additions & 0 deletions LayoutTests/imported/w3c/ChangeLog
@@ -1,3 +1,20 @@
2018-09-12 Ryosuke Niwa <rniwa@webkit.org>

Capturing event listeners are called during bubbling phase for shadow hosts
https://bugs.webkit.org/show_bug.cgi?id=174288

Reviewed by Darin Adler.

* web-platform-tests/dom/events/Event-dispatch-handlers-changed-expected.txt: Rebaselined. This test's
expectation is now wrong because event listner 3 is added after the event listener list is cloned for
capturing event listeners but before cloned for bubbling event listeners. As a result, event listener 3
is now invoked. It used to be not called because both bubbling and capturing event listeners are called
after cloning the event listner list once, which didn't have event listener 3.

* web-platform-tests/dom/events/EventTarget-dispatchEvent-expected.txt: Rebaselined. This test expects
event listener 2, which is bubbling, to be called between two capturing event listeners 1 and 3, which
is no longer true after this patch.

2018-09-12 Chris Dumez <cdumez@apple.com>

Unreviewed, rebaseline imported/w3c/web-platform-tests/url/failure.html after r235808.
Expand Down
@@ -1,3 +1,3 @@

PASS Dispatch additional events inside an event listener
FAIL Dispatch additional events inside an event listener assert_array_equals: actual_targets lengths differ, expected 16 got 17

Expand Up @@ -25,5 +25,5 @@ PASS If the event's initialized flag is not set, an InvalidStateError must be th
PASS If the event's dispatch flag is set, an InvalidStateError must be thrown.
PASS Exceptions from event listeners must not be propagated.
PASS Event listeners added during dispatch should be called
PASS Event listeners should be called in order of addition
FAIL Event listeners should be called in order of addition assert_array_equals: property 1, expected 2 but got 3

2 changes: 1 addition & 1 deletion LayoutTests/media/media-load-event-expected.txt
Expand Up @@ -8,11 +8,11 @@ EVENT(loadstart)
EVENT(durationchange)
EVENT(loadeddata)
EVENT(canplaythrough)
EVENT(canplaythrough)

RUN(document.getElementById('parent').appendChild(mediaElement))
RUN(mediaElement.play())

EVENT(canplaythrough)
EVENT(play)
EVENT(playing)

Expand Down
Expand Up @@ -25,5 +25,5 @@ PASS If the event's initialized flag is not set, an InvalidStateError must be th
PASS If the event's dispatch flag is set, an InvalidStateError must be thrown.
PASS Exceptions from event listeners must not be propagated.
PASS Event listeners added during dispatch should be called
PASS Event listeners should be called in order of addition
FAIL Event listeners should be called in order of addition assert_array_equals: property 1, expected 2 but got 3

Expand Up @@ -25,5 +25,5 @@ PASS If the event's initialized flag is not set, an InvalidStateError must be th
PASS If the event's dispatch flag is set, an InvalidStateError must be thrown.
PASS Exceptions from event listeners must not be propagated.
PASS Event listeners added during dispatch should be called
PASS Event listeners should be called in order of addition
PASS Event listeners should be +FAIL Event listeners should be called in order of addition assert_array_equals: property 1, expected 2 but got 3

84 changes: 84 additions & 0 deletions Source/WebCore/ChangeLog
@@ -1,3 +1,87 @@
2018-09-13 Ryosuke Niwa <rniwa@webkit.org>

Capturing event listeners are called during bubbling phase for shadow hosts
https://bugs.webkit.org/show_bug.cgi?id=174288
<rdar://problem/33530455>

Reviewed by Darin Adler.

Implemented the new behavior proposed in https://github.com/whatwg/dom/pull/686 [1] to fix the problem
that capturing event listeners on a shadow host is invoked during bubbling phase when an event is
dispatched within its shadow tree.

To see why this is a problem, suppose we fire a composed event at span#target in the following DOM tree:
section#hostParent
+ div#host -- ShadowRoot
- p#parent
- span#target
Then capturing and bubbling event listeners on #target, #parent, #host, and #hostParent are invoked in
the following order in WebKit & Chrome right now:

1. #hostParent, capturing, eventPhase: CAPTURING_PHASE
2. #parent, capturing, eventPhase: CAPTURING_PHASE
3. #target, capturing, eventPhase: AT_TARGET
4. #target, non-capturing, eventPhase: AT_TARGET
5. #parent, non-capturing, eventPhase: BUBBLING_PHASE
6. #host, capturing, eventPhase: AT_TARGET
7. #host, non-capturing, eventPhase: AT_TARGET
8. #hostParent, non-capturing, eventPhase: BUBBLING_PHASE

This is counter-intuitive because capturing event listeners on #host isn't invoked until bubblign phase
started. A more natural ordering would be:

1. #hostParent, capturing, eventPhase: CAPTURING_PHASE
2. #host, capturing, eventPhase: AT_TARGET
3. #parent, capturing, eventPhase: CAPTURING_PHASE
4. #target, capturing, eventPhase: AT_TARGET
5. #target, non-capturing, eventPhase: AT_TARGET
6. #parent, non-capturing, eventPhase: BUBBLING_PHASE
7. #host, non-capturing, eventPhase: AT_TARGET
8. #hostParent, non-capturing, eventPhase: BUBBLING_PHASE

This also happens to be the order by which Gecko's current shadow DOM implementation invoke event listners.
This patch implements this new behavior using the spec-change proposed in [1]. Note that this patch also
impacts the invocation order of event listeners when there is no shadow tree. Namely, before this patch,
event listeners on the event's target is invoked in the registration order. After this patch, all capturing
event listeners are invoked before bubbling event listeners are invoked.

To implement this behavior, this patch introduces EventTarget::EventInvokePhase indicating whether we're
in the capturing phase or bubbling phase to EventTarget::fireEventListeners. We can't use Event's eventPhase
enum because that's set to Event::Phase::AT_TARGET when we're at a shadow host.

Test: fast/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html

* Modules/modern-media-controls/media/media-controller-support.js:
(MediaControllerSupport.prototype.enable): Use capturing event listeners so that we can update the states of
media controls before author scripts recieve the event.
(MediaControllerSupport.prototype.disable): Ditto.
* dom/EventContext.cpp:
(WebCore::EventContext::handleLocalEvents const):
(WebCore::MouseOrFocusEventContext::handleLocalEvents const):
(WebCore::TouchEventContext::handleLocalEvents const):
* dom/EventContext.h:
* dom/EventDispatcher.cpp:
(WebCore::dispatchEventInDOM): Invoke capturing event listners even when target and current target are same.
This happens when the current target is a shadow host and event's target is in its shadow tree. Also merged
the special code path for the event's target with the code in the bubbling phase.
* dom/EventPath.cpp:
(WebCore::WindowEventContext::handleLocalEvents const):
* dom/EventTarget.cpp:
(WebCore::EventTarget::dispatchEvent): Invoke capturing and bubbling event listeners in the order.
(WebCore::EventTarget::fireEventListeners):
(WebCore::EventTarget::innerInvokeEventListeners): Renamed from fireEventListeners to match the spec. Use
EventInvokePhase to filter out event listeners so that we can invoke capturing event listners before bubbling
event listeners even when eventPhase is Event::Phase::AT_TARGET.
* dom/EventTarget.h:
* dom/Node.cpp:
(WebCore::Node::handleLocalEvents):
* dom/Node.h:
* html/HTMLFormElement.cpp:
(WebCore::HTMLFormElement::handleLocalEvents):
* html/HTMLFormElement.h:
* page/DOMWindow.cpp:
(WebCore::DOMWindow::dispatchEvent):

2018-09-13 Megan Gardner <megan_gardner@apple.com>

Fix color stop blending in conic gradients for stops past 1
Expand Down

0 comments on commit 83a6595

Please sign in to comment.