Skip to content

Commit

Permalink
refactor(core): Create event types that are able to be serialized, ca…
Browse files Browse the repository at this point in the history
…ptured, or are mouse events. (#55799)

Use these constants across jsaction and Angular.

PR Close #55799
  • Loading branch information
iteriani authored and pkozlowski-opensource committed May 23, 2024
1 parent 01a19f3 commit 89e48aa
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 48 deletions.
6 changes: 6 additions & 0 deletions goldens/public-api/core/primitives/event-dispatch/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ export class EventInfoWrapper {
setTimestamp(timestamp: number): void;
}

// @public
export const isCaptureEvent: (eventType: string) => boolean;

// @public
export const isSupportedEvent: (eventType: string) => boolean;

// @public
export function registerDispatcher(eventContract: UnrenamedEventContract, dispatcher: Dispatcher): void;

Expand Down
1 change: 1 addition & 0 deletions packages/core/primitives/event-dispatch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export {bootstrapEarlyEventContract} from './src/register_events';

export type {EventContractTracker} from './src/register_events';
export {EventInfoWrapper} from './src/event_info';
export {isSupportedEvent, isCaptureEvent} from './src/event_type';
10 changes: 2 additions & 8 deletions packages/core/primitives/event-dispatch/src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import * as dom from './dom';
import {EventHandlerInfo} from './event_handler';
import {EventType} from './event_type';
import {isCaptureEvent, EventType} from './event_type';
import {KeyCode} from './key_code';

/**
Expand Down Expand Up @@ -62,13 +62,7 @@ export function addEventListener(
// handled in the capture phase.
let capture = false;

if (
eventType === EventType.FOCUS ||
eventType === EventType.BLUR ||
eventType === EventType.ERROR ||
eventType === EventType.LOAD ||
eventType === EventType.TOGGLE
) {
if (isCaptureEvent(eventType)) {
capture = true;
}
element.addEventListener(eventType, handler, capture);
Expand Down
96 changes: 96 additions & 0 deletions packages/core/primitives/event-dispatch/src/event_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,99 @@ export const EventType = {
*/
CUSTOM: '_custom',
};

export const NON_BUBBLING_MOUSE_EVENTS = [
EventType.MOUSEENTER,
EventType.MOUSELEAVE,
'pointerenter',
'pointerleave',
];

/**
* Detects whether a given event type is supported by JSAction.
*/
export const isSupportedEvent = (eventType: string) => SUPPORTED_EVENTS.includes(eventType);

const SUPPORTED_EVENTS = [
EventType.CLICK,
EventType.DBLCLICK,
EventType.FOCUS,
EventType.FOCUSIN,
EventType.BLUR,
EventType.ERROR,
EventType.FOCUSOUT,
EventType.KEYDOWN,
EventType.KEYUP,
EventType.KEYPRESS,
EventType.LOAD,
EventType.MOUSEOVER,
EventType.MOUSEOUT,
EventType.SUBMIT,
EventType.TOGGLE,
EventType.TOUCHSTART,
EventType.TOUCHEND,
EventType.TOUCHMOVE,
'touchcancel',

'auxclick',
'change',
'compositionstart',
'compositionupdate',
'compositionend',
'beforeinput',
'input',
'select',

'copy',
'cut',
'paste',
'mousedown',
'mouseup',
'wheel',
'contextmenu',

'dragover',
'dragenter',
'dragleave',
'drop',
'dragstart',
'dragend',

'pointerdown',
'pointermove',
'pointerup',
'pointercancel',
'pointerover',
'pointerout',
'gotpointercapture',
'lostpointercapture',

// Video events.
'ended',
'loadedmetadata',

// Page visibility events.
'pagehide',
'pageshow',
'visibilitychange',

// Content visibility events.
'beforematch',
];

/**
*
* Decides whether or not an event type is an event that only has a capture phase.
*
* @param eventType
* @returns bool
*/
export const isCaptureEvent = (eventType: string) => CAPTURE_EVENTS.indexOf(eventType) >= 0;

const CAPTURE_EVENTS = [
EventType.FOCUS,
EventType.BLUR,
EventType.ERROR,
EventType.LOAD,
EventType.TOGGLE,
];
10 changes: 2 additions & 8 deletions packages/core/primitives/event-dispatch/src/eventcontract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import * as eventLib from './event';
import {EventContractContainerManager} from './event_contract_container';
import {A11Y_CLICK_SUPPORT, MOUSE_SPECIAL_SUPPORT} from './event_contract_defines';
import * as eventInfoLib from './event_info';
import {EventType} from './event_type';
import {EventType, NON_BUBBLING_MOUSE_EVENTS} from './event_type';
import {Restriction} from './restriction';

/**
Expand Down Expand Up @@ -187,13 +187,7 @@ export class EventContract implements UnrenamedEventContract {
return;
}

if (
!EventContract.MOUSE_SPECIAL_SUPPORT &&
(eventType === EventType.MOUSEENTER ||
eventType === EventType.MOUSELEAVE ||
eventType === EventType.POINTERENTER ||
eventType === EventType.POINTERLEAVE)
) {
if (!EventContract.MOUSE_SPECIAL_SUPPORT && NON_BUBBLING_MOUSE_EVENTS.indexOf(eventType) >= 0) {
return;
}

Expand Down
9 changes: 6 additions & 3 deletions packages/core/src/hydration/annotate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export interface HydrationContext {
corruptedTextNodes: Map<HTMLElement, TextNodeMarker>;
isI18nHydrationEnabled: boolean;
i18nChildren: Map<TView, Set<number> | null>;
eventTypesToReplay: Set<string>;
eventTypesToReplay: {regular: Set<string>; capture: Set<string>};
shouldReplayEvents: boolean;
}

Expand Down Expand Up @@ -219,7 +219,10 @@ export function annotateForHydration(appRef: ApplicationRef, doc: Document) {
const corruptedTextNodes = new Map<HTMLElement, TextNodeMarker>();
const viewRefs = appRef._views;
const shouldReplayEvents = injector.get(IS_EVENT_REPLAY_ENABLED, EVENT_REPLAY_ENABLED_DEFAULT);
const eventTypesToReplay = new Set<string>();
const eventTypesToReplay = {
regular: new Set<string>(),
capture: new Set<string>(),
};
for (const viewRef of viewRefs) {
const lNode = getLNodeForHydration(viewRef);

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

/**
Expand Down
19 changes: 11 additions & 8 deletions packages/core/src/hydration/event_replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
EventContractContainer,
EventInfoWrapper,
registerDispatcher,
isSupportedEvent,
isCaptureEvent,
} from '@angular/core/primitives/event-dispatch';

import {APP_BOOTSTRAP_LISTENER, ApplicationRef, whenStable} from '../application/application_ref';
Expand Down Expand Up @@ -121,6 +123,8 @@ export function withEventReplay(): Provider[] {
for (const el of jsactionMap.keys()) {
el.removeAttribute(JSACTION_ATTRIBUTE);
}
// After hydration, we shouldn't need to do anymore work related to
// event replay anymore.
setDisableEventReplayImpl(() => {});
}
});
Expand All @@ -140,7 +144,7 @@ export function withEventReplay(): Provider[] {
export function collectDomEventsInfo(
tView: TView,
lView: LView,
eventTypesToReplay: Set<string>,
eventTypesToReplay: {regular: Set<string>; capture: Set<string>},
): Map<Element, string[]> {
const events = new Map<Element, string[]>();
const lCleanup = lView[CLEANUP];
Expand All @@ -155,15 +159,14 @@ export function collectDomEventsInfo(
continue;
}
const name: string = firstParam;
if (
name === 'mouseenter' ||
name === 'mouseleave' ||
name === 'pointerenter' ||
name === 'pointerleave'
) {
if (!isSupportedEvent(name)) {
continue;
}
eventTypesToReplay.add(name);
if (isCaptureEvent(name)) {
eventTypesToReplay.capture.add(name);
} else {
eventTypesToReplay.regular.add(name);
}
const listenerElement = unwrapRNode(lView[secondParam]) as any as Element;
i++; // move the cursor to the next position (location of the listener idx)
const useCaptureOrIndx = tCleanup[i++];
Expand Down
27 changes: 6 additions & 21 deletions packages/platform-server/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ function prepareForHydration(platformState: PlatformState, applicationRef: Appli

appendSsrContentIntegrityMarker(doc);

const eventTypesToBeReplayed = annotateForHydration(applicationRef, doc);
if (eventTypesToBeReplayed) {
const eventTypesToReplay = annotateForHydration(applicationRef, doc);
if (eventTypesToReplay.regular.size || eventTypesToReplay.capture.size) {
insertEventRecordScript(
environmentInjector.get(APP_ID),
doc,
eventTypesToBeReplayed,
eventTypesToReplay,
environmentInjector.get(CSP_NONCE, null),
);
} else {
Expand Down Expand Up @@ -136,31 +136,16 @@ function appendServerContextInfo(applicationRef: ApplicationRef) {
function insertEventRecordScript(
appId: string,
doc: Document,
eventTypesToBeReplayed: Set<string>,
eventTypesToReplay: {regular: Set<string>; capture: Set<string>},
nonce: string | null,
): void {
const {regular, capture} = eventTypesToReplay;
const eventDispatchScript = findEventDispatchScript(doc);
if (eventDispatchScript) {
const events = Array.from(eventTypesToBeReplayed);
const captureEventTypes = [];
const eventTypes = [];
for (const eventType of events) {
if (
eventType === 'focus' ||
eventType === 'blur' ||
eventType === 'error' ||
eventType === 'load' ||
eventType === 'toggle'
) {
captureEventTypes.push(eventType);
} else {
eventTypes.push(eventType);
}
}
// This is defined in packages/core/primitives/event-dispatch/contract_binary.ts
const replayScriptContents = `window.__jsaction_bootstrap('ngContracts', document.body, ${JSON.stringify(
appId,
)}, ${JSON.stringify(eventTypes)}${captureEventTypes.length ? ',' + JSON.stringify(captureEventTypes) : ''});`;
)}, ${JSON.stringify(Array.from(regular))}${capture.size ? ',' + JSON.stringify(Array.from(capture)) : ''});`;

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

Expand Down

0 comments on commit 89e48aa

Please sign in to comment.