const DOMException = require('domexception/webidl2js-wrapper');
const {nodeRoot} = require('jsdom/lib/jsdom/living/helpers/node');
const reportException = require('jsdom/lib/jsdom/living/helpers/runtime-script-errors');
const {
isNode,
isShadowRoot,
isSlotable,
getEventTargetParent,
isShadowInclusiveAncestor,
retarget,
} = require('jsdom/lib/jsdom/living/helpers/shadow-dom');
const {waitForMicrotasks} = require('./ReactInternalTestUtils');
const EVENT_PHASE = {
NONE: 0,
CAPTURING_PHASE: 1,
AT_TARGET: 2,
BUBBLING_PHASE: 3,
};
let wrapperSymbol;
function wrapperForImpl(impl) {
if (impl == null) {
return null;
}
return impl[wrapperSymbol];
}
async function simulateEventDispatch(eventImpl) {
if (eventImpl._dispatchFlag || !eventImpl._initializedFlag) {
throw DOMException.create(this._globalObject, [
'Tried to dispatch an uninitialized event',
'InvalidStateError',
]);
}
if (eventImpl.eventPhase !== EVENT_PHASE.NONE) {
throw DOMException.create(this._globalObject, [
'Tried to dispatch a dispatching event',
'InvalidStateError',
]);
}
eventImpl.isTrusted = false;
await _dispatch.call(this, eventImpl);
}
async function _dispatch(eventImpl, legacyTargetOverrideFlag) {
wrapperSymbol = Object.getOwnPropertySymbols(eventImpl)[0];
let targetImpl = this;
let clearTargets = false;
let activationTarget = null;
eventImpl._dispatchFlag = true;
const targetOverride = legacyTargetOverrideFlag
? wrapperForImpl(targetImpl._globalObject._document)
: targetImpl;
let relatedTarget = retarget(eventImpl.relatedTarget, targetImpl);
if (targetImpl !== relatedTarget || targetImpl === eventImpl.relatedTarget) {
const touchTargets = [];
appendToEventPath(
eventImpl,
targetImpl,
targetOverride,
relatedTarget,
touchTargets,
false,
);
const isActivationEvent = false;
if (isActivationEvent && targetImpl._hasActivationBehavior) {
activationTarget = targetImpl;
}
let slotInClosedTree = false;
let slotable =
isSlotable(targetImpl) && targetImpl._assignedSlot ? targetImpl : null;
let parent = getEventTargetParent(targetImpl, eventImpl);
while (parent !== null) {
if (slotable !== null) {
if (parent.localName !== 'slot') {
throw new Error(`JSDOM Internal Error: Expected parent to be a Slot`);
}
slotable = null;
const parentRoot = nodeRoot(parent);
if (isShadowRoot(parentRoot) && parentRoot.mode === 'closed') {
slotInClosedTree = true;
}
}
if (isSlotable(parent) && parent._assignedSlot) {
slotable = parent;
}
relatedTarget = retarget(eventImpl.relatedTarget, parent);
if (
(isNode(parent) &&
isShadowInclusiveAncestor(nodeRoot(targetImpl), parent)) ||
wrapperForImpl(parent).constructor.name === 'Window'
) {
if (
isActivationEvent &&
eventImpl.bubbles &&
activationTarget === null &&
parent._hasActivationBehavior
) {
activationTarget = parent;
}
appendToEventPath(
eventImpl,
parent,
null,
relatedTarget,
touchTargets,
slotInClosedTree,
);
} else if (parent === relatedTarget) {
parent = null;
} else {
targetImpl = parent;
if (
isActivationEvent &&
activationTarget === null &&
targetImpl._hasActivationBehavior
) {
activationTarget = targetImpl;
}
appendToEventPath(
eventImpl,
parent,
targetImpl,
relatedTarget,
touchTargets,
slotInClosedTree,
);
}
if (parent !== null) {
parent = getEventTargetParent(parent, eventImpl);
}
slotInClosedTree = false;
}
let clearTargetsStructIndex = -1;
for (
let i = eventImpl._path.length - 1;
i >= 0 && clearTargetsStructIndex === -1;
i--
) {
if (eventImpl._path[i].target !== null) {
clearTargetsStructIndex = i;
}
}
const clearTargetsStruct = eventImpl._path[clearTargetsStructIndex];
clearTargets =
(isNode(clearTargetsStruct.target) &&
isShadowRoot(nodeRoot(clearTargetsStruct.target))) ||
(isNode(clearTargetsStruct.relatedTarget) &&
isShadowRoot(nodeRoot(clearTargetsStruct.relatedTarget)));
if (
activationTarget !== null &&
activationTarget._legacyPreActivationBehavior
) {
activationTarget._legacyPreActivationBehavior();
}
for (let i = eventImpl._path.length - 1; i >= 0; --i) {
const struct = eventImpl._path[i];
if (struct.target !== null) {
eventImpl.eventPhase = EVENT_PHASE.AT_TARGET;
} else {
eventImpl.eventPhase = EVENT_PHASE.CAPTURING_PHASE;
}
await invokeEventListeners(struct, eventImpl, 'capturing');
}
for (let i = 0; i < eventImpl._path.length; i++) {
const struct = eventImpl._path[i];
if (struct.target !== null) {
eventImpl.eventPhase = EVENT_PHASE.AT_TARGET;
} else {
if (!eventImpl.bubbles) {
continue;
}
eventImpl.eventPhase = EVENT_PHASE.BUBBLING_PHASE;
}
await invokeEventListeners(struct, eventImpl, 'bubbling');
}
}
eventImpl.eventPhase = EVENT_PHASE.NONE;
eventImpl.currentTarget = null;
eventImpl._path = [];
eventImpl._dispatchFlag = false;
eventImpl._stopPropagationFlag = false;
eventImpl._stopImmediatePropagationFlag = false;
if (clearTargets) {
eventImpl.target = null;
eventImpl.relatedTarget = null;
}
if (activationTarget !== null) {
if (!eventImpl._canceledFlag) {
activationTarget._activationBehavior(eventImpl);
} else if (activationTarget._legacyCanceledActivationBehavior) {
activationTarget._legacyCanceledActivationBehavior();
}
}
return !eventImpl._canceledFlag;
}
async function invokeEventListeners(struct, eventImpl, phase) {
const structIndex = eventImpl._path.indexOf(struct);
for (let i = structIndex; i >= 0; i--) {
const t = eventImpl._path[i];
if (t.target) {
eventImpl.target = t.target;
break;
}
}
eventImpl.relatedTarget = wrapperForImpl(struct.relatedTarget);
if (eventImpl._stopPropagationFlag) {
return;
}
eventImpl.currentTarget = wrapperForImpl(struct.item);
const listeners = struct.item._eventListeners;
await innerInvokeEventListeners(
eventImpl,
listeners,
phase,
struct.itemInShadowTree,
);
}
async function innerInvokeEventListeners(
eventImpl,
listeners,
phase,
itemInShadowTree,
) {
let found = false;
const {type, target} = eventImpl;
const wrapper = wrapperForImpl(target);
if (!listeners || !listeners[type]) {
return found;
}
const handlers = listeners[type].slice();
for (let i = 0; i < handlers.length; i++) {
const listener = handlers[i];
const {capture, once, passive} = listener.options;
if (!listeners[type].includes(listener)) {
continue;
}
found = true;
if (
(phase === 'capturing' && !capture) ||
(phase === 'bubbling' && capture)
) {
continue;
}
if (once) {
listeners[type].splice(listeners[type].indexOf(listener), 1);
}
let window = null;
if (wrapper && wrapper._document) {
window = wrapper;
} else if (target._ownerDocument) {
window = target._ownerDocument._defaultView;
} else if (wrapper._ownerDocument) {
window = wrapper._ownerDocument._defaultView;
}
let currentEvent;
if (window) {
currentEvent = window._currentEvent;
if (!itemInShadowTree) {
window._currentEvent = eventImpl;
}
}
if (passive) {
eventImpl._inPassiveListenerFlag = true;
}
try {
listener.callback.call(eventImpl.currentTarget, eventImpl);
} catch (e) {
if (window) {
reportException(window, e);
}
}
eventImpl._inPassiveListenerFlag = false;
if (window) {
window._currentEvent = currentEvent;
}
if (eventImpl._stopImmediatePropagationFlag) {
return found;
}
await waitForMicrotasks();
}
return found;
}
function appendToEventPath(
eventImpl,
target,
targetOverride,
relatedTarget,
touchTargets,
slotInClosedTree,
) {
const itemInShadowTree = isNode(target) && isShadowRoot(nodeRoot(target));
const rootOfClosedTree = isShadowRoot(target) && target.mode === 'closed';
eventImpl._path.push({
item: target,
itemInShadowTree,
target: targetOverride,
relatedTarget,
touchTargets,
rootOfClosedTree,
slotInClosedTree,
});
}
export default simulateEventDispatch;