Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event subscription #1149

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 29 additions & 55 deletions src/main-thread/commands/event-subscription.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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;
}
Expand All @@ -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);
Expand All @@ -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,
};
},
};
Expand Down
122 changes: 25 additions & 97 deletions src/test/mutation-transfer/addEventListener.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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<string>, message: MutationFromWorker, buffers: Array<ArrayBuffer>) {
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<string>, message: MutationFromWorker, buffers: Array<ArrayBuffer>) {
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<string>, message: MutationFromWorker, buffers: Array<ArrayBuffer>) {
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;

Expand All @@ -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',
Expand All @@ -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<string>, message: MutationFromWorker, buffers: Array<ArrayBuffer>) {
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);
});
});
12 changes: 4 additions & 8 deletions src/test/mutation-transfer/removeEventListener.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ test.serial.cb('Node.removeEventListener transfers an event subscription', (t) =
function transmitted(strings: Array<string>, message: MutationFromWorker, buffers: Array<ArrayBuffer>) {
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();
Expand All @@ -50,22 +50,18 @@ 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<string>, message: MutationFromWorker, buffers: Array<ArrayBuffer>) {
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'));
div.addEventListener('click', eventHandler);
Promise.resolve().then(() => {
emitter.once(transmitted);
div.removeEventListener('click', eventHandler);
t.end();
});
});
39 changes: 4 additions & 35 deletions src/transfer/TransferrableEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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,
}
Loading