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

refactor(core): Add four tests and fix code to make tests pass. #55747

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
104 changes: 29 additions & 75 deletions packages/core/src/hydration/event_replay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@ import {APP_ID} from '../application/application_tokens';
import {ENVIRONMENT_INITIALIZER, Injector} from '../di';
import {inject} from '../di/injector_compatibility';
import {Provider} from '../di/interface/provider';
import {attachLViewId, readLView} from '../render3/context_discovery';
import {setDisableEventReplayImpl} from '../render3/instructions/listener';
import {TNode, TNodeType} from '../render3/interfaces/node';
import {RElement, RNode} from '../render3/interfaces/renderer_dom';
import {CLEANUP, LView, TVIEW, TView} from '../render3/interfaces/view';
import {RNode} from '../render3/interfaces/renderer_dom';
import {CLEANUP, LView, TView} from '../render3/interfaces/view';
import {isPlatformBrowser} from '../render3/util/misc_utils';
import {unwrapRNode} from '../render3/util/view_utils';

Expand All @@ -43,7 +42,7 @@ function getJsactionData(container: EarlyJsactionDataContainer) {
}

const JSACTION_ATTRIBUTE = 'jsaction';
const removeJsactionQueue: RElement[] = [];
const jsactionMap: Map<Element, Map<string, Function[]>> = new Map();

/**
* Returns a set of providers required to setup support for event replay.
Expand All @@ -58,11 +57,18 @@ export function withEventReplay(): Provider[] {
{
provide: ENVIRONMENT_INITIALIZER,
useValue: () => {
setDisableEventReplayImpl((el: RElement) => {
if (el.hasAttribute(JSACTION_ATTRIBUTE)) {
setDisableEventReplayImpl((rEl, eventName, listenerFn) => {
if (rEl.hasAttribute(JSACTION_ATTRIBUTE)) {
const el = unwrapRNode(rEl) as Element;
// We don't immediately remove the attribute here because
// we need it for replay that happens after hydration.
removeJsactionQueue.push(el);
if (!jsactionMap.has(el)) {
jsactionMap.set(el, new Map());
}
if (!jsactionMap.get(el)!.has(eventName)) {
jsactionMap.get(el)!.set(eventName, []);
}
jsactionMap.get(el)!.get(eventName)!.push(listenerFn);
}
});
},
Expand Down Expand Up @@ -101,14 +107,14 @@ export function withEventReplay(): Provider[] {
for (const event of queue) {
handleEvent(event);
}
jsactionMap.clear();
queue.length = 0;
},
});
registerDispatcher(eventContract, dispatcher);
for (const el of removeJsactionQueue) {
for (const el of jsactionMap.keys()) {
el.removeAttribute(JSACTION_ATTRIBUTE);
}
removeJsactionQueue.length = 0;
}
});
};
Expand Down Expand Up @@ -142,6 +148,14 @@ export function collectDomEventsInfo(
continue;
}
const name: string = firstParam;
if (
name === 'mouseenter' ||
name === 'mouseleave' ||
name === 'pointerenter' ||
name === 'pointerleave'
) {
continue;
}
eventTypesToReplay.add(name);
const listenerElement = unwrapRNode(lView[secondParam]) as any as Element;
i++; // move the cursor to the next position (location of the listener idx)
Expand Down Expand Up @@ -177,73 +191,13 @@ export function setJSActionAttribute(
}
}

/**
* Finds an LView that a given DOM element belongs to.
*/
function getLViewByElement(target: HTMLElement): LView | null {
let lView = readLView(target);
if (lView) {
return lView;
} else {
// If this node doesn't have LView info attached, then we need to
// traverse upwards up the DOM to find the nearest element that
// has already been monkey patched with data.
let parent = target as HTMLElement;
while ((parent = parent.parentNode as HTMLElement)) {
lView = readLView(parent);
if (lView) {
// To prevent additional lookups, monkey-patch LView id onto this DOM node.
// TODO: consider patching all parent nodes that didn't have LView id, so that
// we can avoid lookups for more nodes.
attachLViewId(target, lView);
return lView;
}
}
}
return null;
}

function handleEvent(event: EventInfoWrapper) {
const nativeElement = event.getAction()!.element;
// Dispatch event via Angular's logic
if (nativeElement) {
const lView = getLViewByElement(nativeElement as HTMLElement);
if (lView !== null) {
const tView = lView[TVIEW];
const eventName = event.getEventType();
const origEvent = event.getEvent();
const listeners = getEventListeners(tView, lView, nativeElement, eventName);
for (const listener of listeners) {
listener(origEvent);
}
}
const nativeElement = unwrapRNode(event.getAction()!.element) as Element;
const handlerFns = jsactionMap.get(nativeElement)?.get(event.getEventType());
if (!handlerFns) {
return;
}
}

type Listener = ((value: Event) => unknown) | (() => unknown);

function getEventListeners(
tView: TView,
lView: LView,
nativeElement: Element,
eventName: string,
): Listener[] {
const listeners: Listener[] = [];
const lCleanup = lView[CLEANUP];
const tCleanup = tView.cleanup;
if (tCleanup && lCleanup) {
for (let i = 0; i < tCleanup.length; ) {
const storedEventName = tCleanup[i++];
const nativeElementIndex = tCleanup[i++];
if (typeof storedEventName === 'string') {
const listenerElement = unwrapRNode(lView[nativeElementIndex]) as any as Element;
const listener: Listener = lCleanup[tCleanup[i++]];
i++; // increment to the next position;
if (listenerElement === nativeElement && eventName === storedEventName) {
listeners.push(listener);
}
}
}
for (const handler of handlerFns) {
handler(event.getEvent());
}
return listeners;
}
6 changes: 3 additions & 3 deletions packages/core/src/render3/instructions/listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ import {
* an actual implementation when the event replay feature is enabled via
* `withEventReplay()` call.
*/
let disableEventReplayFn = (el: RElement) => {};
let disableEventReplayFn = (el: RElement, eventName: string, listenerFn: (e?: any) => any) => {};

export function setDisableEventReplayImpl(fn: (el: RElement) => void) {
export function setDisableEventReplayImpl(fn: typeof disableEventReplayFn) {
disableEventReplayFn = fn;
}

Expand Down Expand Up @@ -181,7 +181,7 @@ export function listenerInternal(
? (_lView: LView) => eventTargetResolver(unwrapRNode(_lView[tNode.index]))
: tNode.index;

disableEventReplayFn(native);
disableEventReplayFn(native, eventName, listenerFn);

// In order to match current behavior, native DOM event listeners must be added for all
// events (including outputs).
Expand Down
8 changes: 0 additions & 8 deletions packages/platform-server/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,6 @@ function insertEventRecordScript(
const captureEventTypes = [];
const eventTypes = [];
for (const eventType of events) {
if (
eventType === 'mouseenter' ||
eventType === 'mouseleave' ||
eventType === 'pointerenter' ||
eventType === 'pointerleave'
) {
continue;
}
if (
eventType === 'focus' ||
eventType === 'blur' ||
Expand Down