Skip to content

Intercept createRoot calls and mark as non-interactive #251

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions packages/hyperion-autologging/src/ALDOMSnaptshotPublisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import * as ALSurfaceVisibilityPublisher from "./ALSurfaceVisibilityPublisher";
import { setEventExtension } from "./ALEventExtension";


type TrackingChannels = (ALUIEventPublisher.InitOptions & ALSurfaceVisibilityPublisher.InitOptions)['channel'];
type TrackingChannels = (Omit<ALUIEventPublisher.InitOptions, 'react'> & ALSurfaceVisibilityPublisher.InitOptions)['channel'];
type TrackingEvents = ChannelEventType<TrackingChannels>;
type TrackingEventNames = (keyof TrackingEvents) & ('al_ui_event' | 'al_surface_visibility_event'); // & is added to ensure the event names are a correct subset

Expand Down Expand Up @@ -84,4 +84,4 @@ export function publish(options: InitOptions): void {
}
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { ALSurfaceData } from "./ALSurfaceData";


export type InitOptions = Types.Options<
ALUIEventPublisher.InitOptions &
Omit<ALUIEventPublisher.InitOptions, 'react'> &
ALSharedInitOptions<ChannelEventType<(ALUIEventPublisher.InitOptions & ALSurface.InitOptions & ALSurfaceMutationPublisher.InitOptions)['channel']>>
>;

Expand Down
10 changes: 6 additions & 4 deletions packages/hyperion-autologging/src/ALInteractableDOMElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ type InteractableAncestorCache = {
[index: string]: (Element | null)[];
}

export const IGNORE_INTERACTIVITY_VP = `ignoreInteractivity`;

let getInteractableImpl: (node: Element, eventName: UIEventConfig['eventName'], requireHandlerAssigned: boolean) => Element | null = (node, eventName, requireHandlerAssigned) => {
function getInteractableOptimized(node: Element, eventName: UIEventConfig['eventName'], requireHandlerAssigned: boolean, selectorString?: string): Element | null {
/**
Expand Down Expand Up @@ -152,18 +154,17 @@ let ignoreInteractiveElement: (node: Element) => boolean = node => {
(node.clientHeight === window.innerHeight && node.clientWidth === window.innerWidth);
}

const IgnoreInteractivity = `ignoreInteractivity`;
function ignoreInteractiveElementOptimized(node: Element): boolean {
let shouldIgnore: boolean | undefined;
shouldIgnore = getVirtualPropertyValue<boolean>(node, IgnoreInteractivity);
shouldIgnore = getVirtualPropertyValue<boolean>(node, IGNORE_INTERACTIVITY_VP);
if (shouldIgnore === false || shouldIgnore === true) {
return shouldIgnore;
}
shouldIgnore = ignoreInteractiveElementCore(node);
if (shouldIgnore) {
// In some cases, the size of the component changes slightly and can change the answer from false to true.
// So only caching the true values to be safe.
setVirtualPropertyValue(node, IgnoreInteractivity, shouldIgnore);
setVirtualPropertyValue(node, IGNORE_INTERACTIVITY_VP, shouldIgnore);
}
return shouldIgnore;
}
Expand Down Expand Up @@ -274,6 +275,7 @@ let installHandlers = () => {
}
});


// for (const eventHandler of [
// IGlobalEventHandlers.onabort,
// IGlobalEventHandlers.onanimationcancel,
Expand Down Expand Up @@ -525,7 +527,7 @@ function getTextFromElementsByIds(domSource: ALDOMTextSource, source: ALElementT

for (let i = 0; i < indirectSources.length; i++) {
if (i) {
results.push({ text: " ", source, elements: []}); // Insert space between values
results.push({ text: " ", source, elements: [] }); // Insert space between values
}

domSource.element = indirectSources[i];
Expand Down
33 changes: 30 additions & 3 deletions packages/hyperion-autologging/src/ALUIEventPublisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@
'use strict';
import type * as Types from "hyperion-util/src/Types";

import { intercept } from "hyperion-core/src/intercept";
import { intercept, setVirtualPropertyValue } from "hyperion-core/src/intercept";
import * as IEvent from "hyperion-dom/src/IEvent";
import { setTriggerFlowlet } from "hyperion-flowlet/src/TriggerFlowlet";
import { TimedTrigger } from "hyperion-timed-trigger/src/TimedTrigger";
import performanceAbsoluteNow from "hyperion-util/src/performanceAbsoluteNow";
import ALElementInfo from './ALElementInfo';
import * as ALEventIndex from "./ALEventIndex";
import { ALID, getOrSetAutoLoggingID } from "./ALID";
import { ALElementTextEvent, TrackEventHandlerConfig, enableUIEventHandlers, getElementTextEvent, getInteractable, isTrackedEvent } from "./ALInteractableDOMElement";
import { ALElementTextEvent, IGNORE_INTERACTIVITY_VP, TrackEventHandlerConfig, enableUIEventHandlers, getElementTextEvent, getInteractable, isTrackedEvent } from "./ALInteractableDOMElement";
import { ReactComponentData } from "./ALReactUtils";
import { getSurfacePath } from "./ALSurfaceUtils";
import { ALElementEvent, ALExtensibleEvent, ALFlowletEvent, ALLoggableEvent, ALMetadataEvent, ALPageEvent, ALReactElementEvent, ALSharedInitOptions, ALTimedEvent, Metadata } from "./ALType";
import * as ALUIEventGroupPublisher from "./ALUIEventGroupPublisher";
import * as Flags from "hyperion-globals/src/Flags";
import { getCurrMainPageUrl } from "./MainPageUrl";
import { ALSurfaceData, ALSurfaceEvent } from "./ALSurfaceData";
import * as IReactDOM from "hyperion-react/src/IReactDOM";


/**
Expand Down Expand Up @@ -146,6 +147,9 @@ export type UIEventConfig = UIEventConfigMap[keyof Omit<EventHandlerMap, 'change
export type InitOptions = Types.Options<
ALSharedInitOptions<ALChannelUIEvent> &
{
react: {
IReactDOMClientModule: IReactDOM.IReactDOMClientModuleExports | Promise<IReactDOM.IReactDOMClientModuleExports>;
}
uiEvents: Array<UIEventConfig>;
}
>;
Expand Down Expand Up @@ -239,9 +243,32 @@ export function getCurrentUIEventData(): ALUIEventData | null | undefined {
* and filtering of events is configured via {@link UIEventConfig}.
*/
export function publish(options: InitOptions): void {
const { uiEvents, flowletManager, channel } = options;
const { uiEvents, flowletManager, channel, react } = options;

if (react.IReactDOMClientModule instanceof Promise) {
react.IReactDOMClientModule.then(iReactDOMClientModule => {
publish({
...options,
react: {
...options.react,
IReactDOMClientModule: iReactDOMClientModule,
}
})
});
return;
}
// Initialize root interception, for ignoring these containers as interactable elements
react.IReactDOMClientModule.createRoot.onBeforeCallMapperAdd(args => {
const [container] = args;
if (container instanceof Element) {
setVirtualPropertyValue<boolean>(container, IGNORE_INTERACTIVITY_VP, true);
}

return args;
});


// Track all events
uiEvents.forEach((eventConfig => {
const {
eventName,
Expand Down
5 changes: 3 additions & 2 deletions packages/hyperion-autologging/src/AutoLogging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ type PluginInit = (channel: Channel<ALChannelEvent>) => void;
export type InitOptions = Types.Options<
ALSharedInitOptions<ALChannelEvent> &
{
react: (ALSurface.InitOptions & ALTriggerFlowlet.InitOptions)['react'];
react: (ALSurface.InitOptions & ALTriggerFlowlet.InitOptions & ALUIEventPublisher.InitOptions)['react'];
enableReactComponentVisitors?: boolean;
componentNameValidator?: ComponentNameValidator;
flowletPublisher?: PublicInitOptions<ALFlowletPublisher.InitOptions> | null;
Expand Down Expand Up @@ -190,7 +190,8 @@ export function init(options: InitOptions): boolean {
if (options.uiEventPublisher) {
ALUIEventPublisher.publish({
...sharedOptions,
...options.uiEventPublisher
...options.uiEventPublisher,
react: options.react,
});

ALHoverPublisher.publish({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ import * as ALUIEventPublisher from "../src/ALUIEventPublisher";
import * as DomFragment from "./DomFragment";
import * as ALDOMSnapshotPublisher from "../src/ALDOMSnaptshotPublisher";
import { ALChannelCustomEvent } from "../src/ALCustomEvent";
import * as IReactDOM from "hyperion-react/src/IReactDOM";
import * as ReactDOMClient from "react-dom/client";

describe("dom snapshot publisher", () => {
test("dom snapshot taken for clicks", (done) => {
const flowletManager = new ALFlowletManager();

const channel = new Channel<ALDOMSnapshotPublisher.ALChannelDOMSnapshotPublisherEvent>();
const IReactDOMClientModule = IReactDOM.interceptDOMClient("react-dom/client", ReactDOMClient, []);

ALUIEventPublisher.publish({
flowletManager,
Expand All @@ -27,7 +30,10 @@ describe("dom snapshot publisher", () => {
cacheElementReactInfo: true,
eventName: 'click',
}
]
],
react: {
IReactDOMClientModule
}
});
ALDOMSnapshotPublisher.publish({
flowletManager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ import { Channel } from "hyperion-channel/src/Channel";
import { ALFlowletManager } from "../src/ALFlowletManager";
import * as ALUIEventPublisher from "../src/ALUIEventPublisher";
import * as DomFragment from "./DomFragment";
import * as IReactDOM from "hyperion-react/src/IReactDOM";
import * as ReactDOMClient from "react-dom/client";

describe("UI event publisher", () => {
test("meta data transfer between capture and bubble", (done) => {
const flowletManager = new ALFlowletManager();

const channel = new Channel<ALUIEventPublisher.ALChannelUIEvent>();
const IReactDOMClientModule = IReactDOM.interceptDOMClient("react-dom/client", ReactDOMClient, []);

ALUIEventPublisher.publish({
flowletManager,
Expand All @@ -25,7 +28,10 @@ describe("UI event publisher", () => {
cacheElementReactInfo: true,
eventName: 'click',
}
]
],
react: {
IReactDOMClientModule,
}
});

channel.addListener('al_ui_event_capture', event => {
Expand Down
5 changes: 4 additions & 1 deletion packages/hyperion-react-testapp/src/AutoLoggingWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as IReactDOM from "hyperion-react/src/IReactDOM";
import { ClientSessionID, getDomainSessionID } from "hyperion-util/src/ClientSessionID";
import React from 'react';
import * as ReactDOM from "react-dom";
import * as ReactDOMClient from "react-dom/client";
import ReactDev from "react/jsx-dev-runtime";
import { SyncChannel } from "./Channel";
import { FlowletManager } from "./FlowletManager";
Expand Down Expand Up @@ -37,7 +38,8 @@ export function init() {

const IReactModule = IReact.intercept("react", React, [])
const IJsxRuntimeModule = IReact.interceptRuntime("react/jsx-dev-runtime", ReactDev as any, []);
const IReactDOMModule = IReactDOM.intercept("react-dom", ReactDOM, []);
const IReactDOMModule = IReactDOM.interceptDOM("react-dom", ReactDOM, []);
const IReactDOMClientModule = IReactDOM.interceptDOMClient("react-dom/client", ReactDOMClient, []);

const channel = SyncChannel;

Expand Down Expand Up @@ -82,6 +84,7 @@ export function init() {
react: {
ReactModule: React as any,
IReactDOMModule,
IReactDOMClientModule,
IReactModule,
IJsxRuntimeModule,
},
Expand Down
18 changes: 17 additions & 1 deletion packages/hyperion-react/src/IReactDOM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import type ReactDOM from "react-dom";
import type { createRoot } from "react-dom/client";

import { InterceptedModuleExports, interceptModuleExports, ModuleExportsKeys } from 'hyperion-core/src/IRequire';

Expand All @@ -13,9 +14,24 @@ export type ReactDOMModuleExports = {
export type IReactDOMModuleExports = InterceptedModuleExports<ReactDOMModuleExports>;
let IReactDOMModule: IReactDOMModuleExports | null = null;

export function intercept(moduleId: string, moduleExports: ReactDOMModuleExports, failedExportsKeys?: ModuleExportsKeys<ReactDOMModuleExports>): IReactDOMModuleExports {
export type ReactDOMClientModuleExports = {
createRoot: typeof createRoot;
}

export type IReactDOMClientModuleExports = InterceptedModuleExports<ReactDOMClientModuleExports>;
let IReactDOMClientModule: IReactDOMClientModuleExports | null = null;


export function interceptDOM(moduleId: string, moduleExports: ReactDOMModuleExports, failedExportsKeys?: ModuleExportsKeys<ReactDOMModuleExports>): IReactDOMModuleExports {
if (!IReactDOMModule) {
IReactDOMModule = interceptModuleExports(moduleId, moduleExports, ['createPortal'], failedExportsKeys);
}
return IReactDOMModule;
}

export function interceptDOMClient(moduleId: string, moduleExports: ReactDOMClientModuleExports, failedExportsKeys?: ModuleExportsKeys<ReactDOMClientModuleExports>): IReactDOMClientModuleExports {
if (!IReactDOMClientModule) {
IReactDOMClientModule = interceptModuleExports(moduleId, moduleExports, ['createRoot'], failedExportsKeys);
}
return IReactDOMClientModule;
}