diff --git a/packages/core/primitives/event-dispatch/src/dispatcher.ts b/packages/core/primitives/event-dispatch/src/dispatcher.ts index d15efccc3e764..a08d655d97485 100644 --- a/packages/core/primitives/event-dispatch/src/dispatcher.ts +++ b/packages/core/primitives/event-dispatch/src/dispatcher.ts @@ -78,6 +78,10 @@ export class Dispatcher { */ dispatch(eventInfo: EventInfo): void { const eventInfoWrapper = new EventInfoWrapper(eventInfo); + const action = eventInfoWrapper.getAction(); + if (action && shouldPreventDefaultBeforeDispatching(action.element, eventInfoWrapper)) { + eventLib.preventDefault(eventInfoWrapper.getEvent()); + } if (eventInfoWrapper.getIsReplay()) { if (!this.eventReplayer) { return; @@ -131,6 +135,24 @@ export function stopPropagation(eventInfoWrapper: EventInfoWrapper) { event.stopPropagation(); } +/** + * Returns true if the default action of this event should be prevented before + * this event is dispatched. + */ +function shouldPreventDefaultBeforeDispatching( + actionElement: Element, + eventInfoWrapper: EventInfoWrapper, +): boolean { + // Prevent browser from following node links if a jsaction is present + // and we are dispatching the action now. Note that the targetElement may be + // a child of an anchor that has a jsaction attached. For that reason, we + // need to check the actionElement rather than the targetElement. + return ( + (actionElement.tagName === 'A' && eventInfoWrapper.getEventType() === EventType.CLICK) || + eventInfoWrapper.getEventType() === EventType.CLICKMOD + ); +} + /** * Registers deferred functionality for an EventContract and a Jsaction * Dispatcher. diff --git a/packages/core/primitives/event-dispatch/src/eventcontract.ts b/packages/core/primitives/event-dispatch/src/eventcontract.ts index eb6316f1b2f75..e4a95e1e79af2 100644 --- a/packages/core/primitives/event-dispatch/src/eventcontract.ts +++ b/packages/core/primitives/event-dispatch/src/eventcontract.ts @@ -157,13 +157,6 @@ export class EventContract implements UnrenamedEventContract { return; } this.actionResolver.resolve(eventInfo); - const action = eventInfoLib.getAction(eventInfo); - if (action) { - if (shouldPreventDefaultBeforeDispatching(eventInfoLib.getActionElement(action), eventInfo)) { - eventLib.preventDefault(eventInfoLib.getEvent(eventInfo)); - } - } - this.dispatcher(eventInfo); } @@ -386,22 +379,3 @@ export function addDeferredA11yClickSupport(eventContract: EventContract) { a11yClickLib.populateClickOnlyAction, ); } - -/** - * Returns true if the default action of this event should be prevented before - * this event is dispatched. - */ -function shouldPreventDefaultBeforeDispatching( - actionElement: Element, - eventInfo: eventInfoLib.EventInfo, -): boolean { - // Prevent browser from following node links if a jsaction is present - // and we are dispatching the action now. Note that the targetElement may be - // a child of an anchor that has a jsaction attached. For that reason, we - // need to check the actionElement rather than the targetElement. - return ( - actionElement.tagName === 'A' && - (eventInfoLib.getEventType(eventInfo) === EventType.CLICK || - eventInfoLib.getEventType(eventInfo) === EventType.CLICKMOD) - ); -} diff --git a/packages/core/primitives/event-dispatch/test/eventcontract_test.ts b/packages/core/primitives/event-dispatch/test/eventcontract_test.ts index 1569bc1c3a5bb..98d3f6c5ef103 100644 --- a/packages/core/primitives/event-dispatch/test/eventcontract_test.ts +++ b/packages/core/primitives/event-dispatch/test/eventcontract_test.ts @@ -25,6 +25,10 @@ import {OWNER} from '../src/property'; import {Restriction} from '../src/restriction'; import {safeElement, testonlyHtml} from './html'; +import { + Dispatcher as LateDispatcher, + registerDispatcher as registerLateDispatcher, +} from '../src/dispatcher'; declare global { interface Window extends EarlyJsactionDataContainer {} @@ -849,17 +853,18 @@ describe('EventContract', () => { const actionElement = getRequiredElementById('anchor-click-action-element'); const targetElement = getRequiredElementById('anchor-click-target-element'); - const dispatcher = jasmine.createSpy('dispatcher'); - createEventContract({ + const eventContract = createEventContract({ eventContractContainerManager: new EventContractContainer(container), eventTypes: ['click'], - dispatcher, }); + const dispatch = jasmine.createSpy<(eventInfoWrapper: EventInfoWrapper) => void>('dispatch'); + const dispatcher = new LateDispatcher(dispatch); + registerLateDispatcher(eventContract, dispatcher); const clickEvent = dispatchMouseEvent(targetElement); - expect(dispatcher).toHaveBeenCalledTimes(1); - const eventInfoWrapper = getLastDispatchedEventInfoWrapper(dispatcher); + expect(dispatch).toHaveBeenCalledTimes(1); + const eventInfoWrapper = dispatch.calls.mostRecent().args[0]; expect(eventInfoWrapper.getEventType()).toBe('click'); expect(eventInfoWrapper.getEvent()).toBe(clickEvent); expect(eventInfoWrapper.getTargetElement()).toBe(targetElement); @@ -874,17 +879,18 @@ describe('EventContract', () => { const actionElement = getRequiredElementById('anchor-clickmod-action-element'); const targetElement = getRequiredElementById('anchor-clickmod-target-element'); - const dispatcher = jasmine.createSpy('dispatcher'); - createEventContract({ + const eventContract = createEventContract({ eventContractContainerManager: new EventContractContainer(container), eventTypes: ['click'], - dispatcher, }); + const dispatch = jasmine.createSpy<(eventInfoWrapper: EventInfoWrapper) => void>('dispatch'); + const dispatcher = new LateDispatcher(dispatch); + registerLateDispatcher(eventContract, dispatcher); const clickEvent = dispatchMouseEvent(targetElement, {shiftKey: true}); - expect(dispatcher).toHaveBeenCalledTimes(1); - const eventInfoWrapper = getLastDispatchedEventInfoWrapper(dispatcher); + expect(dispatch).toHaveBeenCalledTimes(1); + const eventInfoWrapper = dispatch.calls.mostRecent().args[0]; expect(eventInfoWrapper.getEventType()).toBe('clickmod'); expect(eventInfoWrapper.getEvent()).toBe(clickEvent); expect(eventInfoWrapper.getTargetElement()).toBe(targetElement); @@ -966,17 +972,18 @@ describe('EventContract', () => { const actionElement = getRequiredElementById('a11y-anchor-click-action-element'); const targetElement = getRequiredElementById('a11y-anchor-click-target-element'); - const dispatcher = jasmine.createSpy('dispatcher'); - createEventContract({ + const eventContract = createEventContract({ eventContractContainerManager: new EventContractContainer(container), eventTypes: ['click'], - dispatcher, }); + const dispatch = jasmine.createSpy<(eventInfoWrapper: EventInfoWrapper) => void>('dispatch'); + const dispatcher = new LateDispatcher(dispatch); + registerLateDispatcher(eventContract, dispatcher); const keydownEvent = dispatchKeyboardEvent(targetElement, {key: 'Enter'}); - expect(dispatcher).toHaveBeenCalledTimes(1); - const eventInfoWrapper = getLastDispatchedEventInfoWrapper(dispatcher); + expect(dispatch).toHaveBeenCalledTimes(1); + const eventInfoWrapper = dispatch.calls.mostRecent().args[0]; expect(eventInfoWrapper.getEventType()).toBe('click'); expect(eventInfoWrapper.getEvent()).toBe(keydownEvent); expect(eventInfoWrapper.getTargetElement()).toBe(targetElement); @@ -1095,19 +1102,20 @@ describe('EventContract', () => { const actionElement = getRequiredElementById('a11y-anchor-click-action-element'); const targetElement = getRequiredElementById('a11y-anchor-click-target-element'); - const dispatcher = jasmine.createSpy('dispatcher'); const eventContract = createEventContract({ eventContractContainerManager: new EventContractContainer(container), exportAddA11yClickSupport: true, eventTypes: ['click'], - dispatcher, }); addDeferredA11yClickSupport(eventContract); + const dispatch = jasmine.createSpy<(eventInfoWrapper: EventInfoWrapper) => void>('dispatch'); + const dispatcher = new LateDispatcher(dispatch); + registerLateDispatcher(eventContract, dispatcher); const keydownEvent = dispatchKeyboardEvent(targetElement, {key: 'Enter'}); - expect(dispatcher).toHaveBeenCalledTimes(1); - const eventInfoWrapper = getLastDispatchedEventInfoWrapper(dispatcher); + expect(dispatch).toHaveBeenCalledTimes(1); + const eventInfoWrapper = dispatch.calls.mostRecent().args[0]; expect(eventInfoWrapper.getEventType()).toBe('click'); expect(eventInfoWrapper.getEvent()).toBe(keydownEvent); expect(eventInfoWrapper.getTargetElement()).toBe(targetElement);