Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions .changeset/cruel-feet-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/react": patch
---

Add dual-thread commutation logs for troubleshooting when `REACT_ALOG=true` or global define `__ALOG__` is set.
50 changes: 50 additions & 0 deletions packages/react/runtime/__test__/alog/elementPAPICall.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, it, vi } from 'vitest';
import { initElementPAPICallAlog } from '../../src/alog/elementPAPICall';
import { globalEnvManager } from '../utils/envManager';
import { expect } from 'vitest';

describe('ElementPAPICall Alog', () => {
it('should log ElementPAPICall as ALog', () => {
globalEnvManager.switchToMainThread();
console.alog = vi.fn();
initElementPAPICallAlog();

const page = __CreatePage('0', 0);
__SetCSSId([page], 0);
const text0 = __CreateText(0);
const rawText0 = __CreateRawText('2', text0.$$uiSign);
__AppendElement(text0, rawText0);
__AppendElement(page, text0);
__AddDataset(text0, 'testid', 'count-value');
__OnLifecycleEvent(['rLynxFirstScreen', { 'root': '{}', 'jsReadyEventIdSwap': {} }]);

expect(console.alog.mock.calls).toMatchInlineSnapshot(`
[
[
"[ReactLynxDebug] FiberElement API call #1: __CreatePage("0", 0) => page#0",
],
[
"[ReactLynxDebug] FiberElement API call #2: __SetCSSId([page#0], 0)",
],
[
"[ReactLynxDebug] FiberElement API call #3: __CreateText(0) => text#1",
],
[
"[ReactLynxDebug] FiberElement API call #4: __CreateRawText("2", 1) => raw-text#2",
],
[
"[ReactLynxDebug] FiberElement API call #5: __AppendElement(text#1, raw-text#2)",
],
[
"[ReactLynxDebug] FiberElement API call #6: __AppendElement(page#0, text#1)",
],
[
"[ReactLynxDebug] FiberElement API call #7: __AddDataset(text#1, "testid", "count-value")",
],
[
"[ReactLynxDebug] FiberElement API call #8: __OnLifecycleEvent(["rLynxFirstScreen", {"root":"{}","jsReadyEventIdSwap":{}}])",
],
]
`);
});
});
131 changes: 131 additions & 0 deletions packages/react/runtime/src/alog/elementPAPICall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2025 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.

import { profileEnd, profileStart } from '../debug/utils.js';

const fiberElementPAPINameList = [
Comment thread
upupming marked this conversation as resolved.
'__CreatePage',
'__CreateElement',
'__CreateWrapperElement',
'__CreateText',
'__CreateImage',
'__CreateView',
'__CreateRawText',
'__CreateList',
'__AppendElement',
'__InsertElementBefore',
'__RemoveElement',
'__ReplaceElement',
'__FirstElement',
'__LastElement',
'__NextElement',
'__GetPageElement',
'__GetTemplateParts',
'__AddDataset',
'__SetDataset',
'__GetDataset',
'__SetAttribute',
'__GetAttributes',
'__GetAttributeByName',
'__GetAttributeNames',
'__SetClasses',
'__SetCSSId',
'__AddInlineStyle',
'__SetInlineStyles',
'__AddEvent',
'__SetID',
'__GetElementUniqueID',
'__GetTag',
'__FlushElementTree',
'__UpdateListCallbacks',
'__OnLifecycleEvent',
'__QueryComponent',
'__SetGestureDetector',
];
Comment thread
coderabbitai[bot] marked this conversation as resolved.

export function initElementPAPICallAlog(globalWithIndex: Record<string, unknown> = globalThis): void {
let count = 0;
const fiberElementMap = new Map<any, {
tag: string;
uniqueId: number;
}>();
function formatFiberElement(fiberElement: FiberElement): string {
const fiberElementInfo = fiberElementMap.get(fiberElement)!;
return `${fiberElementInfo.tag}#${fiberElementInfo.uniqueId}`;
}
const filteredFiberElementPAPINameList = fiberElementPAPINameList.filter(fiberElementPAPIName =>
typeof globalWithIndex[fiberElementPAPIName] === 'function'
);
const originalFiberElementPAPIs = filteredFiberElementPAPINameList.reduce((prev, fiberElementPAPIName) => ({
...prev,
[fiberElementPAPIName]: globalWithIndex[fiberElementPAPIName] as (...args: unknown[]) => unknown,
}), {} as Record<string, (...args: unknown[]) => unknown>);

filteredFiberElementPAPINameList.forEach(fiberElementPAPIName => {
const oldFiberElementPAPI = globalWithIndex[fiberElementPAPIName];
if (typeof oldFiberElementPAPI === 'function') {
globalWithIndex[fiberElementPAPIName] = (...args: unknown[]): unknown => {
if (__PROFILE__) {
Comment thread
upupming marked this conversation as resolved.
profileStart(`FiberElementPAPI: ${fiberElementPAPIName}`, {
args: {
args: JSON.stringify(args),
},
});
}
const result = (oldFiberElementPAPI as (...args: unknown[]) => unknown)(...args);
if (__PROFILE__) {
profileEnd();
}

const formattedArgs = [...args];

for (let i = 0; i < formattedArgs.length; i++) {
const arg = formattedArgs[i];
if (Array.isArray(arg)) {
formattedArgs[i] = '[' + arg.map((item: unknown) => {
if (fiberElementMap.has(item)) {
return formatFiberElement(item as FiberElement);
}
return JSON.stringify(item);
}).join(', ') + ']';
} else if (fiberElementMap.has(arg)) {
formattedArgs[i] = formatFiberElement(arg as FiberElement);
} else {
formattedArgs[i] = JSON.stringify(arg);
}
}

if (
fiberElementPAPIName === '__CreatePage'
|| fiberElementPAPIName === '__CreateElement'
|| fiberElementPAPIName === '__CreateWrapperElement'
|| fiberElementPAPIName === '__CreateText'
|| fiberElementPAPIName === '__CreateImage'
|| fiberElementPAPIName === '__CreateView'
|| fiberElementPAPIName === '__CreateRawText'
|| fiberElementPAPIName === '__CreateList'
) {
fiberElementMap.set(result as FiberElement, {
tag: originalFiberElementPAPIs['__GetTag']!(result as FiberElement) as string,
uniqueId: originalFiberElementPAPIs['__GetElementUniqueID']!(result as FiberElement) as number,
});
Comment thread
upupming marked this conversation as resolved.
}

let formattedResult;
if (fiberElementMap.has(result)) {
formattedResult = formatFiberElement(result as FiberElement);
} else if (result !== null) {
formattedResult = JSON.stringify(result);
}

console.alog?.(
`[ReactLynxDebug] FiberElement API call #${++count}: ${fiberElementPAPIName}(${formattedArgs.join(', ')})${
formattedResult == null ? '' : ` => ${formattedResult}`
}`,
);
return result;
};
}
});
}
2 changes: 2 additions & 0 deletions packages/react/runtime/src/alog/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright 2025 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
import { initElementPAPICallAlog } from './elementPAPICall.js';
import { initRenderAlog } from './render.js';

export function initAlog(): void {
initRenderAlog();
initElementPAPICallAlog();
}
23 changes: 22 additions & 1 deletion packages/react/runtime/src/lifecycle/patch/updateMainThread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { runRunOnMainThreadTask, setEomShouldFlushElementTree } from '@lynx-js/r
import type { PatchList, PatchOptions } from './commit.js';
import { setMainThreadHydrating } from './isMainThreadHydrating.js';
import { snapshotPatchApply } from './snapshotPatchApply.js';
import { prettyFormatSnapshotPatch } from '../../debug/formatPatch.js';
import { LifecycleConstant } from '../../lifecycleConstant.js';
import { markTiming, setPipeline } from '../../lynx/performance.js';
import { __pendingListUpdates } from '../../pendingListUpdates.js';
Expand Down Expand Up @@ -37,7 +38,27 @@ function updateMainThread(
setPipeline(patchOptions.pipelineOptions);
markTiming('mtsRenderStart');
markTiming('parseChangesStart');
const { patchList, flushOptions = {}, delayedRunOnMainThreadData } = JSON.parse(data) as PatchList;
const parsedData = JSON.parse(data) as PatchList;
const { patchList, flushOptions = {}, delayedRunOnMainThreadData } = parsedData;

if (typeof __ALOG__ !== 'undefined' && __ALOG__) {
console.alog?.(
'[ReactLynxDebug] BTS -> MTS updateMainThread:\n' + JSON.stringify(
{
data: {
...parsedData,
patchList: parsedData.patchList.map(patch => ({
...patch,
snapshotPatch: prettyFormatSnapshotPatch(patch.snapshotPatch),
})),
},
patchOptions,
},
null,
2,
),
);
}

markTiming('parseChangesEnd');
markTiming('patchChangesStart');
Expand Down
13 changes: 13 additions & 0 deletions packages/react/runtime/src/lynx/tt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ function onLifecycleEventImpl(type: LifecycleConstant, data: unknown): void {
beginPipeline(true, PipelineOrigins.reactLynxHydrate, PerformanceTimingFlags.reactLynxHydrate);
markTiming('hydrateParseSnapshotStart');
const before = JSON.parse(lepusSide) as SerializedSnapshotInstance;
if (typeof __ALOG__ !== 'undefined' && __ALOG__) {
console.alog?.(
'[ReactLynxDebug] MTS -> BTS OnLifecycleEvent:\n' + JSON.stringify(
{
...data as object,
// use parsed lepusSide to avoid extra escape characters ('\\')
root: before,
},
null,
2,
),
);
}
markTiming('hydrateParseSnapshotEnd');
markTiming('diffVdomStart');
const snapshotPatch = hydrate(
Expand Down
Loading
Loading