diff --git a/src/main-thread/commands/event-subscription.ts b/src/main-thread/commands/event-subscription.ts index 455fa1c06..1d000d348 100644 --- a/src/main-thread/commands/event-subscription.ts +++ b/src/main-thread/commands/event-subscription.ts @@ -1,11 +1,8 @@ import { MessageType } from '../../transfer/Messages'; import { TransferrableKeys } from '../../transfer/TransferrableKeys'; import { - ADD_EVENT_SUBSCRIPTION_LENGTH, - REMOVE_EVENT_SUBSCRIPTION_LENGTH, EventSubscriptionMutationIndex, TransferrableTouchList, - AddEventRegistrationIndex, } from '../../transfer/TransferrableEvent'; import { WorkerContext } from '../worker'; import { CommandExecutorInterface } from './interface'; @@ -95,7 +92,6 @@ const createTransferrableTouchList = (touchList: TouchList): TransferrableTouchL ]); export const EventSubscriptionProcessor: CommandExecutorInterface = (strings, nodeContext, workerContext, objectContext, config) => { - const knownListeners: Array<(event: Event) => any> = []; const allowedExecution = config.executorsAllowed.includes(TransferrableMutationType.EVENT_SUBSCRIPTION); let cachedWindowSize: [number, number] = [window.innerWidth, window.innerHeight]; @@ -153,20 +149,22 @@ export const EventSubscriptionProcessor: CommandExecutorInterface = (strings, no * If the worker requests to add an event listener to 'change' for something the foreground thread is already listening to, * ensure that only a single 'change' event is attached to prevent sending values multiple times. * @param target node to change listeners on + * @param type event type * @param addEvent is this an 'addEvent' or 'removeEvent' change - * @param mutations Uint16Array for this set of changes - * @param iterator current location in array to perform this change on + * @param preventDefault prevent default flag, use only if addEvent is true */ - const processListenerChange = (target: RenderableElement, addEvent: boolean, mutations: Uint16Array, iterator: number): void => { - const type = strings.get(mutations[iterator]); - const eventIndex = mutations[iterator + AddEventRegistrationIndex.Index]; + const processListenerChange = (target: RenderableElement, type: string, addEvent: boolean, preventDefault: boolean): void => { + target._knownListeners_ = target._knownListeners_ || {} as {[key: string] : (event: Event) => any}; if (target === nodeContext.baseElement) { if (addEvent) { - const preventDefault = Boolean(mutations[iterator + AddEventRegistrationIndex.WorkerDOMPreventDefault]); - addEventListener(type, (knownListeners[eventIndex] = eventHandler(BASE_ELEMENT_INDEX, preventDefault))); + if (target._knownListeners_[type]) { + removeEventListener(type, target._knownListeners_[type]); + } + addEventListener(type, (target._knownListeners_[type] = eventHandler(BASE_ELEMENT_INDEX, preventDefault))); } else { - removeEventListener(type, knownListeners[eventIndex]); + removeEventListener(type, target._knownListeners_[type]); + delete target._knownListeners_[type]; } return; } @@ -178,13 +176,16 @@ export const EventSubscriptionProcessor: CommandExecutorInterface = (strings, no inputEventSubscribed = true; target.onchange = null; } - const preventDefault = Boolean(mutations[iterator + AddEventRegistrationIndex.WorkerDOMPreventDefault]); - (target as HTMLElement).addEventListener(type, (knownListeners[eventIndex] = eventHandler(target._index_, preventDefault))); + if (target._knownListeners_[type]) { + (target as HTMLElement).removeEventListener(type, target._knownListeners_[type]); + } + (target as HTMLElement).addEventListener(type, (target._knownListeners_[type] = eventHandler(target._index_, preventDefault))); } else { if (isChangeEvent) { inputEventSubscribed = false; } - (target as HTMLElement).removeEventListener(type, knownListeners[eventIndex]); + (target as HTMLElement).removeEventListener(type, target._knownListeners_[type]); + delete target._knownListeners_[type]; } if (shouldTrackChanges(target as HTMLElement)) { if (!inputEventSubscribed) applyDefaultInputListener(workerContext, target as RenderableElement); @@ -194,65 +195,38 @@ export const EventSubscriptionProcessor: CommandExecutorInterface = (strings, no return { execute(mutations: Uint16Array, startPosition: number, allowedMutation: boolean): number { - const addEventListenerCount = mutations[startPosition + EventSubscriptionMutationIndex.AddEventListenerCount]; - const removeEventListenerCount = mutations[startPosition + EventSubscriptionMutationIndex.RemoveEventListenerCount]; - const addEventListenersPosition = - startPosition + EventSubscriptionMutationIndex.Events + removeEventListenerCount * REMOVE_EVENT_SUBSCRIPTION_LENGTH; - const endPosition = - startPosition + - EventSubscriptionMutationIndex.Events + - addEventListenerCount * ADD_EVENT_SUBSCRIPTION_LENGTH + - removeEventListenerCount * REMOVE_EVENT_SUBSCRIPTION_LENGTH; if (allowedExecution && allowedMutation) { const targetIndex = mutations[startPosition + EventSubscriptionMutationIndex.Target]; const target = nodeContext.getNode(targetIndex); if (target) { - let iterator = startPosition + EventSubscriptionMutationIndex.Events; - while (iterator < endPosition) { - const isRemoveEvent = iterator <= addEventListenersPosition; - processListenerChange(target, isRemoveEvent, mutations, iterator); - iterator += isRemoveEvent ? REMOVE_EVENT_SUBSCRIPTION_LENGTH : ADD_EVENT_SUBSCRIPTION_LENGTH; - } + const type = strings.get(mutations[startPosition + EventSubscriptionMutationIndex.EventType]); + const addEvent = mutations[startPosition + EventSubscriptionMutationIndex.IsAddEvent] === 1; + const preventDefault = addEvent ? Boolean(mutations[startPosition + EventSubscriptionMutationIndex.PreventDefault]) : false; + + processListenerChange(target, type, addEvent, preventDefault); + } else { console.error(`getNode(${targetIndex}) is null.`); } } - return endPosition; + return startPosition + EventSubscriptionMutationIndex.End; }, print(mutations: Uint16Array, startPosition: number): {} { - const addEventListenerCount = mutations[startPosition + EventSubscriptionMutationIndex.AddEventListenerCount]; - const removeEventListenerCount = mutations[startPosition + EventSubscriptionMutationIndex.RemoveEventListenerCount]; - const addEventListenersPosition = - startPosition + EventSubscriptionMutationIndex.Events + removeEventListenerCount * REMOVE_EVENT_SUBSCRIPTION_LENGTH; - const endPosition = - startPosition + - EventSubscriptionMutationIndex.Events + - addEventListenerCount * ADD_EVENT_SUBSCRIPTION_LENGTH + - removeEventListenerCount * REMOVE_EVENT_SUBSCRIPTION_LENGTH; const targetIndex = mutations[startPosition + EventSubscriptionMutationIndex.Target]; const target = nodeContext.getNode(targetIndex); - const removedEventListeners: Array<{ type: string; index: number }> = []; - const addedEventListeners: Array<{ type: string; index: number }> = []; - - let iterator = startPosition + EventSubscriptionMutationIndex.Events; - while (iterator < endPosition) { - const isRemoveEvent = iterator <= addEventListenersPosition; - const eventList = isRemoveEvent ? addedEventListeners : removedEventListeners; - eventList.push({ - type: strings.get(mutations[iterator]), - index: mutations[iterator + 1], - }); - iterator += isRemoveEvent ? REMOVE_EVENT_SUBSCRIPTION_LENGTH : ADD_EVENT_SUBSCRIPTION_LENGTH; - } + const type = strings.get(mutations[startPosition + EventSubscriptionMutationIndex.EventType]); + const addEvent = mutations[startPosition + EventSubscriptionMutationIndex.IsAddEvent] === 1; + const preventDefault = addEvent ? Boolean(mutations[startPosition + EventSubscriptionMutationIndex.EventType]) : false; return { target, allowedExecution, - removedEventListeners, - addedEventListeners, + type, + addEvent, + preventDefault, }; }, }; diff --git a/src/test/mutation-transfer/addEventListener.test.ts b/src/test/mutation-transfer/addEventListener.test.ts index 58d56da6b..4f9bd87fd 100644 --- a/src/test/mutation-transfer/addEventListener.test.ts +++ b/src/test/mutation-transfer/addEventListener.test.ts @@ -41,13 +41,8 @@ test.serial.cb('Node.addEventListener transfers an event subscription', (t) => { [ TransferrableMutationType.EVENT_SUBSCRIPTION, div[TransferrableKeys.index], - 0, 1, strings.indexOf('click'), - 0, // This is the first event registered. - NumericBoolean.FALSE, - NumericBoolean.FALSE, - NumericBoolean.FALSE, NumericBoolean.FALSE, ], 'mutation is as expected', @@ -61,93 +56,6 @@ test.serial.cb('Node.addEventListener transfers an event subscription', (t) => { }); }); -test.serial.cb('Node.addEventListener(..., {capture: true}) transfers an event subscription', (t) => { - const { div, eventHandler, emitter } = t.context; - - function transmitted(strings: Array, message: MutationFromWorker, buffers: Array) { - t.deepEqual( - Array.from(new Uint16Array(message[TransferrableKeys.mutations])), - [ - TransferrableMutationType.EVENT_SUBSCRIPTION, - div[TransferrableKeys.index], - 0, - 1, - strings.indexOf('click'), - 0, // This is the first event registered. - NumericBoolean.TRUE, - NumericBoolean.FALSE, - NumericBoolean.FALSE, - NumericBoolean.FALSE, - ], - 'mutation is as expected', - ); - t.end(); - } - - Promise.resolve().then(() => { - emitter.once(transmitted); - div.addEventListener('click', eventHandler, { capture: true }); - }); -}); - -test.serial.cb('Node.addEventListener(..., {once: true}) transfers an event subscription', (t) => { - const { div, eventHandler, emitter } = t.context; - - function transmitted(strings: Array, message: MutationFromWorker, buffers: Array) { - t.deepEqual( - Array.from(new Uint16Array(message[TransferrableKeys.mutations])), - [ - TransferrableMutationType.EVENT_SUBSCRIPTION, - div[TransferrableKeys.index], - 0, - 1, - strings.indexOf('click'), - 0, // This is the first event registered. - NumericBoolean.FALSE, - NumericBoolean.TRUE, - NumericBoolean.FALSE, - NumericBoolean.FALSE, - ], - 'mutation is as expected', - ); - t.end(); - } - - Promise.resolve().then(() => { - emitter.once(transmitted); - div.addEventListener('click', eventHandler, { once: true }); - }); -}); - -test.serial.cb('Node.addEventListener(..., {passive: true}) transfers an event subscription', (t) => { - const { div, eventHandler, emitter } = t.context; - - function transmitted(strings: Array, message: MutationFromWorker, buffers: Array) { - t.deepEqual( - Array.from(new Uint16Array(message[TransferrableKeys.mutations])), - [ - TransferrableMutationType.EVENT_SUBSCRIPTION, - div[TransferrableKeys.index], - 0, - 1, - strings.indexOf('click'), - 0, // This is the first event registered. - NumericBoolean.FALSE, - NumericBoolean.FALSE, - NumericBoolean.TRUE, - NumericBoolean.FALSE, - ], - 'mutation is as expected', - ); - t.end(); - } - - Promise.resolve().then(() => { - emitter.once(transmitted); - div.addEventListener('click', eventHandler, { passive: true }); - }); -}); - test.serial.cb('Node.addEventListener(..., {workerDOMPreventDefault: true}) transfers an event subscription', (t) => { const { div, eventHandler, emitter } = t.context; @@ -157,13 +65,8 @@ test.serial.cb('Node.addEventListener(..., {workerDOMPreventDefault: true}) tran [ TransferrableMutationType.EVENT_SUBSCRIPTION, div[TransferrableKeys.index], - 0, 1, strings.indexOf('click'), - 0, // This is the first event registered. - NumericBoolean.FALSE, - NumericBoolean.FALSE, - NumericBoolean.FALSE, NumericBoolean.TRUE, ], 'mutation is as expected', @@ -178,3 +81,28 @@ test.serial.cb('Node.addEventListener(..., {workerDOMPreventDefault: true}) tran }); }); }); + +test.serial.cb('Node.addEventListener transfers an event subscription only once', (t) => { + const { div, eventHandler, emitter } = t.context; + + function transmitted(strings: Array, message: MutationFromWorker, buffers: Array) { + t.deepEqual( + Array.from(new Uint16Array(message[TransferrableKeys.mutations])), + [ + TransferrableMutationType.EVENT_SUBSCRIPTION, + div[TransferrableKeys.index], + 1, + strings.indexOf('click'), + NumericBoolean.FALSE, + ], + 'mutation is as expected', + ); + t.end(); + } + + Promise.resolve().then(() => { + emitter.once(transmitted); + div.addEventListener('click', (e) => console.log('0th listener')); + div.addEventListener('click', eventHandler); + }); +}); diff --git a/src/test/mutation-transfer/removeEventListener.test.ts b/src/test/mutation-transfer/removeEventListener.test.ts index 21b5e70a8..118ba8d1f 100644 --- a/src/test/mutation-transfer/removeEventListener.test.ts +++ b/src/test/mutation-transfer/removeEventListener.test.ts @@ -37,7 +37,7 @@ test.serial.cb('Node.removeEventListener transfers an event subscription', (t) = function transmitted(strings: Array, message: MutationFromWorker, buffers: Array) { t.deepEqual( Array.from(new Uint16Array(message[TransferrableKeys.mutations])), - [TransferrableMutationType.EVENT_SUBSCRIPTION, div[TransferrableKeys.index], 1, 0, strings.indexOf('click'), 0], + [TransferrableMutationType.EVENT_SUBSCRIPTION, div[TransferrableKeys.index], 0, strings.indexOf('click'), 0], 'mutation is as expected', ); t.end(); @@ -50,16 +50,11 @@ test.serial.cb('Node.removeEventListener transfers an event subscription', (t) = }); }); -test.serial.cb('Node.removeEventListener transfers the correct subscription when multiple exist', (t) => { +test.serial.cb('Node.removeEventListener not transfers the subscription when multiple exist', (t) => { const { div, eventHandler, emitter } = t.context; function transmitted(strings: Array, message: MutationFromWorker, buffers: Array) { - t.deepEqual( - Array.from(new Uint16Array(message[TransferrableKeys.mutations])), - [TransferrableMutationType.EVENT_SUBSCRIPTION, div[TransferrableKeys.index], 1, 0, strings.indexOf('click'), 1], - 'mutation is as expected', - ); - t.end(); + throw 'Should not be called'; } div.addEventListener('click', (e) => console.log('0th listener')); @@ -67,5 +62,6 @@ test.serial.cb('Node.removeEventListener transfers the correct subscription when Promise.resolve().then(() => { emitter.once(transmitted); div.removeEventListener('click', eventHandler); + t.end(); }); }); diff --git a/src/transfer/TransferrableEvent.ts b/src/transfer/TransferrableEvent.ts index 9326f60e0..e649c5709 100644 --- a/src/transfer/TransferrableEvent.ts +++ b/src/transfer/TransferrableEvent.ts @@ -42,37 +42,6 @@ export interface TransferrableEvent { readonly [TransferrableKeys.changedTouches]?: TransferrableTouchList; } -/** - * Add Event Registration Transfer - * - * [ - * type, - * index, - * capture, - * once, - * passive, - * workerDOMPreventDefault - * ] - */ -export const enum AddEventRegistrationIndex { - Type = 0, - Index = 1, - Capture = 2, - Once = 3, - Passive = 4, - WorkerDOMPreventDefault = 5, -} -export const ADD_EVENT_SUBSCRIPTION_LENGTH = 6; - -/** - * Remove Event Registration Transfer - */ -export const enum RemoveEventRegistrationIndex { - Type = 0, - Index = 1, -} -export const REMOVE_EVENT_SUBSCRIPTION_LENGTH = 2; - /** * Event Subscription Transfer * @@ -87,8 +56,8 @@ export const REMOVE_EVENT_SUBSCRIPTION_LENGTH = 2; */ export const enum EventSubscriptionMutationIndex { Target = 1, - RemoveEventListenerCount = 2, - AddEventListenerCount = 3, - Events = 4, - End = 4, + IsAddEvent = 2, + EventType = 3, + PreventDefault = 4, + End = 5, } diff --git a/src/worker-thread/dom/Node.ts b/src/worker-thread/dom/Node.ts index 32ca2b4cd..08162241d 100644 --- a/src/worker-thread/dom/Node.ts +++ b/src/worker-thread/dom/Node.ts @@ -408,27 +408,19 @@ export abstract class Node { */ public addEventListener(type: string, handler: EventHandler, options: AddEventListenerOptions | undefined = {}): void { const lowerType = toLower(type); - const storedType = storeString(lowerType); const handlers: EventHandler[] = this[TransferrableKeys.handlers][lowerType]; - let index: number = 0; - if (handlers) { - index = handlers.push(handler); + if (handlers && handlers.length > 0) { + handlers.push(handler); } else { this[TransferrableKeys.handlers][lowerType] = [handler]; + transfer(this.ownerDocument as Document, [ + TransferrableMutationType.EVENT_SUBSCRIPTION, + this[TransferrableKeys.index], + 1, + storeString(lowerType), + Number(Boolean(options.workerDOMPreventDefault)), + ]); } - - transfer(this.ownerDocument as Document, [ - TransferrableMutationType.EVENT_SUBSCRIPTION, - this[TransferrableKeys.index], - 0, - 1, - storedType, - index, - Number(Boolean(options.capture)), - Number(Boolean(options.once)), - Number(Boolean(options.passive)), - Number(Boolean(options.workerDOMPreventDefault)), - ]); } /** @@ -444,14 +436,15 @@ export abstract class Node { if (index >= 0) { handlers.splice(index, 1); - transfer(this.ownerDocument as Document, [ - TransferrableMutationType.EVENT_SUBSCRIPTION, - this[TransferrableKeys.index], - 1, - 0, - storeString(lowerType), - index, - ]); + if (handlers.length == 0) { + transfer(this.ownerDocument as Document, [ + TransferrableMutationType.EVENT_SUBSCRIPTION, + this[TransferrableKeys.index], + 0, + storeString(lowerType), + 0 + ]); + } } }