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
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":{}}])",
],
]
`);
});
});
63 changes: 62 additions & 1 deletion packages/react/runtime/__test__/debug/printSnapshot.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { backgroundSnapshotInstanceManager, snapshotInstanceManager } from '../../src/snapshot';
import { elementTree } from '../utils/nativeMethod';
import { BackgroundSnapshotInstance } from '../../src/backgroundSnapshot';
import { printSnapshotInstance } from '../../src/debug/printSnapshot';
import { printSerializedSnapshotInstance, printSnapshotInstance } from '../../src/debug/printSnapshot';
import { SnapshotInstance } from '../../src/snapshot';

const HOLE = null;

Expand Down Expand Up @@ -72,6 +73,40 @@ describe('printSnapshotInstance', () => {
`);
});

it('SnapshotInstance', async function() {
const si1 = new SnapshotInstance(snapshot1);
const si2 = new SnapshotInstance(snapshot2);
const si22 = new SnapshotInstance(snapshot2);
const si3 = new SnapshotInstance(snapshot3);
si1.insertBefore(si2);
si1.insertBefore(si22);
si2.insertBefore(si3);
si3.setAttribute(0, 'attr 1');
si3.setAttribute(1, 'attr 2');
si3.setAttribute('__1', 'attr 2');
printSnapshotInstance(si1, log);
expect(msg).toMatchInlineSnapshot(`
"
| -2(__snapshot_a94a8_test_1): undefined
| -3(__snapshot_a94a8_test_2): undefined
| -5(__snapshot_a94a8_test_3): ["attr 1","attr 2"]
| -4(__snapshot_a94a8_test_2): undefined
"
`);

const serialized = JSON.stringify(si1);
msg = '\n';
printSerializedSnapshotInstance(JSON.parse(serialized), log);
expect(msg).toMatchInlineSnapshot(`
"
| -2(__snapshot_a94a8_test_1): undefined
| -3(__snapshot_a94a8_test_2): undefined
| -5(__snapshot_a94a8_test_3): ["attr 1","attr 2"]
| -4(__snapshot_a94a8_test_2): undefined
"
`);
});

it('printToScreen', async function() {
const bsi1 = new BackgroundSnapshotInstance(snapshot1);
const bsi2 = new BackgroundSnapshotInstance(snapshot2);
Expand All @@ -88,4 +123,30 @@ describe('printSnapshotInstance', () => {
"
`);
});

it('printToScreen for SnapshotInstance', async function() {
const si1 = new SnapshotInstance(snapshot1);
const si2 = new SnapshotInstance(snapshot2);
const si22 = new SnapshotInstance(snapshot2);
const si3 = new SnapshotInstance(snapshot3);
si1.insertBefore(si2);
si1.insertBefore(si22);
si2.insertBefore(si3);
si3.setAttribute(0, 'attr 1');
si3.setAttribute(1, 'attr 2');
si3.setAttribute('__1', 'attr 2');
printSnapshotInstance(si1);
expect(msg).toMatchInlineSnapshot(`
"
"
`);

const serialized = JSON.stringify(si1);
msg = '\n';
printSerializedSnapshotInstance(JSON.parse(serialized));
expect(msg).toMatchInlineSnapshot(`
"
"
`);
});
});
14 changes: 7 additions & 7 deletions packages/react/runtime/__test__/snapshotPatch.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -289,19 +289,19 @@ describe('insertBefore', () => {
expect(_ReportError.mock.calls).toMatchInlineSnapshot(`
[
[
[Error: snapshotPatchApply failed: ctx not found, snapshot type: 'null'],
[Error: snapshotPatchApply failed: ctx not found, snapshot type: 'null'. You can set environment variable \`REACT_ALOG=true\` and restart your dev server for troubleshooting.],
{
"errorCode": 1101,
},
],
[
[Error: snapshotPatchApply failed: ctx not found, snapshot type: 'null'],
[Error: snapshotPatchApply failed: ctx not found, snapshot type: 'null'. You can set environment variable \`REACT_ALOG=true\` and restart your dev server for troubleshooting.],
{
"errorCode": 1101,
},
],
[
[Error: snapshotPatchApply failed: ctx not found, snapshot type: '__snapshot_a94a8_test_3'],
[Error: snapshotPatchApply failed: ctx not found, snapshot type: '__snapshot_a94a8_test_3'. You can set environment variable \`REACT_ALOG=true\` and restart your dev server for troubleshooting.],
{
"errorCode": 1101,
},
Expand Down Expand Up @@ -464,13 +464,13 @@ describe('removeChild', () => {
expect(_ReportError.mock.calls).toMatchInlineSnapshot(`
[
[
[Error: snapshotPatchApply failed: ctx not found, snapshot type: 'root'],
[Error: snapshotPatchApply failed: ctx not found, snapshot type: 'root'. You can set environment variable \`REACT_ALOG=true\` and restart your dev server for troubleshooting.],
{
"errorCode": 1101,
},
],
[
[Error: snapshotPatchApply failed: ctx not found, snapshot type: 'root'],
[Error: snapshotPatchApply failed: ctx not found, snapshot type: 'root'. You can set environment variable \`REACT_ALOG=true\` and restart your dev server for troubleshooting.],
{
"errorCode": 1101,
},
Expand Down Expand Up @@ -656,7 +656,7 @@ describe('setAttribute', () => {
expect(_ReportError.mock.calls).toMatchInlineSnapshot(`
[
[
[Error: snapshotPatchApply failed: ctx not found, snapshot type: 'null'],
[Error: snapshotPatchApply failed: ctx not found, snapshot type: 'null'. You can set environment variable \`REACT_ALOG=true\` and restart your dev server for troubleshooting.],
{
"errorCode": 1101,
},
Expand Down Expand Up @@ -700,7 +700,7 @@ describe('setAttribute', () => {

expect(_ReportError.mock.calls[0]).toMatchInlineSnapshot(`
[
[Error: snapshotPatchApply failed: ctx not found, snapshot type: 'null'],
[Error: snapshotPatchApply failed: ctx not found, snapshot type: 'null'. You can set environment variable \`REACT_ALOG=true\` and restart your dev server for troubleshooting.],
{
"errorCode": 1101,
},
Expand Down
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();
}
37 changes: 36 additions & 1 deletion packages/react/runtime/src/debug/printSnapshot.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Copyright 2024 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 type { BackgroundSnapshotInstance } from '../backgroundSnapshot.js';
import { BackgroundSnapshotInstance } from '../backgroundSnapshot.js';
import { SnapshotInstance } from '../snapshot.js';
import type { SerializedSnapshotInstance } from '../snapshot.js';
import { logDebug } from './debug.js';

export function printSnapshotInstance(
Expand All @@ -26,3 +27,37 @@ export function printSnapshotInstance(

impl(instance, 0);
}

export function printSerializedSnapshotInstance(
instance: SerializedSnapshotInstance,
log?: (...data: any[]) => void,
): void {
const impl = (
instance: SerializedSnapshotInstance,
level: number,
) => {
let msg = '';
for (let i = 0; i < level; ++i) {
msg += ' ';
}
msg += `| ${instance.id}(${instance.type}): ${JSON.stringify(instance.values)}`;
(log ?? logDebug)(msg);
for (const c of instance.children ?? []) {
impl(c, level + 1);
}
};

impl(instance, 0);
}

export function printSnapshotInstanceToString(
instance: SnapshotInstance | BackgroundSnapshotInstance | SerializedSnapshotInstance,
): string {
const logArr: string[] = [];
if (instance instanceof SnapshotInstance || instance instanceof BackgroundSnapshotInstance) {
printSnapshotInstance(instance, logArr.push.bind(logArr));
} else {
printSerializedSnapshotInstance(instance, logArr.push.bind(logArr));
}
return logArr.join('\n');
}
Loading
Loading