Description
We have a bug report saying that WebKit fires event listeners with capture flag set to true during bubbling phase.
Consider the following DOM:
const hostParent = document.createElement('section');
hostParent.id = 'hostParent';
hostParent.innerHTML = '<div id="host"></div>';
const host = hostParent.firstChild;
const shadowRoot = host.attachShadow({mode: 'closed'});
shadowRoot.innerHTML = '<p id="parent"><span id="target"></span></p>';
...
target.dispatchEvent(new CustomEvent('test', {detail: {}, bubbles: true, composed: true}));
Then the event listeners on target, parent, host, and hostParent are invoked in the following order on WebKit & Chrome:
- hostParent, capturing, eventPhase: CAPTURING_PHASE
- parent, capturing, eventPhase: CAPTURING_PHASE
- target, capturing, eventPhase: AT_TARGET
- target, non-capturing, eventPhase: AT_TARGET
- parent, non-capturing, eventPhase: BUBBLING_PHASE
- host, capturing, eventPhase: AT_TARGET
- host, non-capturing, eventPhase: AT_TARGET
- hostParent, non-capturing, eventPhase: BUBBLING_PHASE
As far as I can tell, the current behavior of WebKit & Chrome is in accordance with the latest DOM specification. What happens is that step 5 (event path construction) in dispatching an event ends up appending an tuple/struct (we should probably stick with either term, btw) with target set to null
. In step 14, we only invoke event listeners with capture flag set if target is null
, meaning that shadow hosts' event listeners with capture flag set would NOT be called. Then in step 15, we set eventPhase
to AT_TARGET
and invoke event listeners. Because the concept to inner invoke an event listener doesn't skip event listeners with capture flag set to true when the event phase is AT_TARGET, we end up firing capturing event listeners during bubbling.