Skip to content

Commit 89e48aa

Browse files
iterianipkozlowski-opensource
authored andcommitted
refactor(core): Create event types that are able to be serialized, captured, or are mouse events. (#55799)
Use these constants across jsaction and Angular. PR Close #55799
1 parent 01a19f3 commit 89e48aa

8 files changed

Lines changed: 130 additions & 48 deletions

File tree

goldens/public-api/core/primitives/event-dispatch/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ export class EventInfoWrapper {
101101
setTimestamp(timestamp: number): void;
102102
}
103103

104+
// @public
105+
export const isCaptureEvent: (eventType: string) => boolean;
106+
107+
// @public
108+
export const isSupportedEvent: (eventType: string) => boolean;
109+
104110
// @public
105111
export function registerDispatcher(eventContract: UnrenamedEventContract, dispatcher: Dispatcher): void;
106112

packages/core/primitives/event-dispatch/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export {bootstrapEarlyEventContract} from './src/register_events';
1414

1515
export type {EventContractTracker} from './src/register_events';
1616
export {EventInfoWrapper} from './src/event_info';
17+
export {isSupportedEvent, isCaptureEvent} from './src/event_type';

packages/core/primitives/event-dispatch/src/event.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import * as dom from './dom';
1010
import {EventHandlerInfo} from './event_handler';
11-
import {EventType} from './event_type';
11+
import {isCaptureEvent, EventType} from './event_type';
1212
import {KeyCode} from './key_code';
1313

1414
/**
@@ -62,13 +62,7 @@ export function addEventListener(
6262
// handled in the capture phase.
6363
let capture = false;
6464

65-
if (
66-
eventType === EventType.FOCUS ||
67-
eventType === EventType.BLUR ||
68-
eventType === EventType.ERROR ||
69-
eventType === EventType.LOAD ||
70-
eventType === EventType.TOGGLE
71-
) {
65+
if (isCaptureEvent(eventType)) {
7266
capture = true;
7367
}
7468
element.addEventListener(eventType, handler, capture);

packages/core/primitives/event-dispatch/src/event_type.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,3 +282,99 @@ export const EventType = {
282282
*/
283283
CUSTOM: '_custom',
284284
};
285+
286+
export const NON_BUBBLING_MOUSE_EVENTS = [
287+
EventType.MOUSEENTER,
288+
EventType.MOUSELEAVE,
289+
'pointerenter',
290+
'pointerleave',
291+
];
292+
293+
/**
294+
* Detects whether a given event type is supported by JSAction.
295+
*/
296+
export const isSupportedEvent = (eventType: string) => SUPPORTED_EVENTS.includes(eventType);
297+
298+
const SUPPORTED_EVENTS = [
299+
EventType.CLICK,
300+
EventType.DBLCLICK,
301+
EventType.FOCUS,
302+
EventType.FOCUSIN,
303+
EventType.BLUR,
304+
EventType.ERROR,
305+
EventType.FOCUSOUT,
306+
EventType.KEYDOWN,
307+
EventType.KEYUP,
308+
EventType.KEYPRESS,
309+
EventType.LOAD,
310+
EventType.MOUSEOVER,
311+
EventType.MOUSEOUT,
312+
EventType.SUBMIT,
313+
EventType.TOGGLE,
314+
EventType.TOUCHSTART,
315+
EventType.TOUCHEND,
316+
EventType.TOUCHMOVE,
317+
'touchcancel',
318+
319+
'auxclick',
320+
'change',
321+
'compositionstart',
322+
'compositionupdate',
323+
'compositionend',
324+
'beforeinput',
325+
'input',
326+
'select',
327+
328+
'copy',
329+
'cut',
330+
'paste',
331+
'mousedown',
332+
'mouseup',
333+
'wheel',
334+
'contextmenu',
335+
336+
'dragover',
337+
'dragenter',
338+
'dragleave',
339+
'drop',
340+
'dragstart',
341+
'dragend',
342+
343+
'pointerdown',
344+
'pointermove',
345+
'pointerup',
346+
'pointercancel',
347+
'pointerover',
348+
'pointerout',
349+
'gotpointercapture',
350+
'lostpointercapture',
351+
352+
// Video events.
353+
'ended',
354+
'loadedmetadata',
355+
356+
// Page visibility events.
357+
'pagehide',
358+
'pageshow',
359+
'visibilitychange',
360+
361+
// Content visibility events.
362+
'beforematch',
363+
];
364+
365+
/**
366+
*
367+
* Decides whether or not an event type is an event that only has a capture phase.
368+
*
369+
* @param eventType
370+
* @returns bool
371+
*/
372+
export const isCaptureEvent = (eventType: string) => CAPTURE_EVENTS.indexOf(eventType) >= 0;
373+
374+
const CAPTURE_EVENTS = [
375+
EventType.FOCUS,
376+
EventType.BLUR,
377+
EventType.ERROR,
378+
EventType.LOAD,
379+
EventType.TOGGLE,
380+
];

packages/core/primitives/event-dispatch/src/eventcontract.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import * as eventLib from './event';
3737
import {EventContractContainerManager} from './event_contract_container';
3838
import {A11Y_CLICK_SUPPORT, MOUSE_SPECIAL_SUPPORT} from './event_contract_defines';
3939
import * as eventInfoLib from './event_info';
40-
import {EventType} from './event_type';
40+
import {EventType, NON_BUBBLING_MOUSE_EVENTS} from './event_type';
4141
import {Restriction} from './restriction';
4242

4343
/**
@@ -187,13 +187,7 @@ export class EventContract implements UnrenamedEventContract {
187187
return;
188188
}
189189

190-
if (
191-
!EventContract.MOUSE_SPECIAL_SUPPORT &&
192-
(eventType === EventType.MOUSEENTER ||
193-
eventType === EventType.MOUSELEAVE ||
194-
eventType === EventType.POINTERENTER ||
195-
eventType === EventType.POINTERLEAVE)
196-
) {
190+
if (!EventContract.MOUSE_SPECIAL_SUPPORT && NON_BUBBLING_MOUSE_EVENTS.indexOf(eventType) >= 0) {
197191
return;
198192
}
199193

packages/core/src/hydration/annotate.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export interface HydrationContext {
130130
corruptedTextNodes: Map<HTMLElement, TextNodeMarker>;
131131
isI18nHydrationEnabled: boolean;
132132
i18nChildren: Map<TView, Set<number> | null>;
133-
eventTypesToReplay: Set<string>;
133+
eventTypesToReplay: {regular: Set<string>; capture: Set<string>};
134134
shouldReplayEvents: boolean;
135135
}
136136

@@ -219,7 +219,10 @@ export function annotateForHydration(appRef: ApplicationRef, doc: Document) {
219219
const corruptedTextNodes = new Map<HTMLElement, TextNodeMarker>();
220220
const viewRefs = appRef._views;
221221
const shouldReplayEvents = injector.get(IS_EVENT_REPLAY_ENABLED, EVENT_REPLAY_ENABLED_DEFAULT);
222-
const eventTypesToReplay = new Set<string>();
222+
const eventTypesToReplay = {
223+
regular: new Set<string>(),
224+
capture: new Set<string>(),
225+
};
223226
for (const viewRef of viewRefs) {
224227
const lNode = getLNodeForHydration(viewRef);
225228

@@ -251,7 +254,7 @@ export function annotateForHydration(appRef: ApplicationRef, doc: Document) {
251254
const serializedViews = serializedViewCollection.getAll();
252255
const transferState = injector.get(TransferState);
253256
transferState.set(NGH_DATA_KEY, serializedViews);
254-
return eventTypesToReplay.size > 0 ? eventTypesToReplay : undefined;
257+
return eventTypesToReplay;
255258
}
256259

257260
/**

packages/core/src/hydration/event_replay.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
EventContractContainer,
1414
EventInfoWrapper,
1515
registerDispatcher,
16+
isSupportedEvent,
17+
isCaptureEvent,
1618
} from '@angular/core/primitives/event-dispatch';
1719

1820
import {APP_BOOTSTRAP_LISTENER, ApplicationRef, whenStable} from '../application/application_ref';
@@ -121,6 +123,8 @@ export function withEventReplay(): Provider[] {
121123
for (const el of jsactionMap.keys()) {
122124
el.removeAttribute(JSACTION_ATTRIBUTE);
123125
}
126+
// After hydration, we shouldn't need to do anymore work related to
127+
// event replay anymore.
124128
setDisableEventReplayImpl(() => {});
125129
}
126130
});
@@ -140,7 +144,7 @@ export function withEventReplay(): Provider[] {
140144
export function collectDomEventsInfo(
141145
tView: TView,
142146
lView: LView,
143-
eventTypesToReplay: Set<string>,
147+
eventTypesToReplay: {regular: Set<string>; capture: Set<string>},
144148
): Map<Element, string[]> {
145149
const events = new Map<Element, string[]>();
146150
const lCleanup = lView[CLEANUP];
@@ -155,15 +159,14 @@ export function collectDomEventsInfo(
155159
continue;
156160
}
157161
const name: string = firstParam;
158-
if (
159-
name === 'mouseenter' ||
160-
name === 'mouseleave' ||
161-
name === 'pointerenter' ||
162-
name === 'pointerleave'
163-
) {
162+
if (!isSupportedEvent(name)) {
164163
continue;
165164
}
166-
eventTypesToReplay.add(name);
165+
if (isCaptureEvent(name)) {
166+
eventTypesToReplay.capture.add(name);
167+
} else {
168+
eventTypesToReplay.regular.add(name);
169+
}
167170
const listenerElement = unwrapRNode(lView[secondParam]) as any as Element;
168171
i++; // move the cursor to the next position (location of the listener idx)
169172
const useCaptureOrIndx = tCleanup[i++];

packages/platform-server/src/utils.ts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ function prepareForHydration(platformState: PlatformState, applicationRef: Appli
8888

8989
appendSsrContentIntegrityMarker(doc);
9090

91-
const eventTypesToBeReplayed = annotateForHydration(applicationRef, doc);
92-
if (eventTypesToBeReplayed) {
91+
const eventTypesToReplay = annotateForHydration(applicationRef, doc);
92+
if (eventTypesToReplay.regular.size || eventTypesToReplay.capture.size) {
9393
insertEventRecordScript(
9494
environmentInjector.get(APP_ID),
9595
doc,
96-
eventTypesToBeReplayed,
96+
eventTypesToReplay,
9797
environmentInjector.get(CSP_NONCE, null),
9898
);
9999
} else {
@@ -136,31 +136,16 @@ function appendServerContextInfo(applicationRef: ApplicationRef) {
136136
function insertEventRecordScript(
137137
appId: string,
138138
doc: Document,
139-
eventTypesToBeReplayed: Set<string>,
139+
eventTypesToReplay: {regular: Set<string>; capture: Set<string>},
140140
nonce: string | null,
141141
): void {
142+
const {regular, capture} = eventTypesToReplay;
142143
const eventDispatchScript = findEventDispatchScript(doc);
143144
if (eventDispatchScript) {
144-
const events = Array.from(eventTypesToBeReplayed);
145-
const captureEventTypes = [];
146-
const eventTypes = [];
147-
for (const eventType of events) {
148-
if (
149-
eventType === 'focus' ||
150-
eventType === 'blur' ||
151-
eventType === 'error' ||
152-
eventType === 'load' ||
153-
eventType === 'toggle'
154-
) {
155-
captureEventTypes.push(eventType);
156-
} else {
157-
eventTypes.push(eventType);
158-
}
159-
}
160145
// This is defined in packages/core/primitives/event-dispatch/contract_binary.ts
161146
const replayScriptContents = `window.__jsaction_bootstrap('ngContracts', document.body, ${JSON.stringify(
162147
appId,
163-
)}, ${JSON.stringify(eventTypes)}${captureEventTypes.length ? ',' + JSON.stringify(captureEventTypes) : ''});`;
148+
)}, ${JSON.stringify(Array.from(regular))}${capture.size ? ',' + JSON.stringify(Array.from(capture)) : ''});`;
164149

165150
const replayScript = createScript(doc, replayScriptContents, nonce);
166151

0 commit comments

Comments
 (0)