Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,33 @@ describe('startTracking with injected jsx runtimes', () => {
);
});

it("M leave React's own factory alone W the app declared its runtime", async () => {
// A styling library that owns the element factory can route React.createElement
// through machinery of its own. Replacing it then feeds that machinery calls it was
// never written to receive - measured on a release build as the heap growing until
// Hermes aborted at startup. Once the app has told us where its JSX comes from,
// there is nothing to gain there and a crash to lose.
const before = React.createElement;
const runtime: Record<string, unknown> = {
jsx: jest.fn(),
jsxs: jest.fn()
};

DdRumUserInteractionTracking.startTracking({}, [runtime]);

expect(React.createElement).toBe(before);
// the declared runtime is still instrumented - that is what records the taps
expect(runtime.jsx).not.toBe(before);
});

it("M patch React's own factory W no runtime was declared", async () => {
const before = React.createElement;

DdRumUserInteractionTracking.startTracking({});

expect(React.createElement).not.toBe(before);
});

it('M restore the injected runtime W stopTracking is called', async () => {
const jsx = jest.fn();
const jsxs = jest.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,17 +243,37 @@ export class DdRumUserInteractionTracking {
options
);

// React's own factory is left alone once the app has told us which runtime it
// compiles to. Two reasons, and the second one is why this is not merely tidy.
//
// Under the automatic JSX transform such an app never calls React.createElement -
// its elements come from the runtime it declared - so patching it buys no action.
//
// And it is not free. A styling library that owns the element factory can route
// React.createElement through machinery of its own; replacing it then feeds that
// machinery calls it was never written to receive, including React's internal ones.
// Measured on a release build of a nativewind app: the heap grew without bound until
// Hermes aborted during startup. Skipping this patch there is what stops the crash -
// replacing the factory more carefully does not, which two earlier attempts at this
// established the hard way.
//
// `memo` stays patched either way: it only restores the original onPress for
// comparison, and was measured not to contribute to the crash.
const appDeclaredItsRuntime = jsxRuntimes.length > 0;

const originalCreateElement = reactModule['createElement'];
replaceProperty(
reactModule,
'createElement',
(...args: Parameters<typeof React.createElement>): any => {
return this.patchCreateElementFunction(
originalCreateElement,
args
);
}
);
if (!appDeclaredItsRuntime) {
replaceProperty(
reactModule,
'createElement',
(...args: Parameters<typeof React.createElement>): any => {
return this.patchCreateElementFunction(
originalCreateElement,
args
);
}
);
}

const runtimes: JsxRuntimeModule[] = [];
try {
Expand Down
Loading