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
10 changes: 10 additions & 0 deletions .changeset/loose-crews-slide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@lynx-js/web-core": patch
"@lynx-js/web-mainthread-apis": patch
---

fix: mts freeze after reload()

The mts may be freezed after reload() called.

We fixed it by waiting until the all-on-ui Javascript realm implementation, an iframe, to be fully loaded.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
type StartMainThreadContextConfig,
type RpcCallType,
updateDataEndpoint,
type MainThreadGlobalThis,
type I18nResourceTranslationOptions,
type CloneableObject,
i18nResourceMissedEventName,
Expand Down Expand Up @@ -38,27 +37,32 @@ const {
* Creates a isolated JavaScript context for executing mts code.
* This context has its own global variables and functions.
*/
function createIFrameRealm(parent: Node): JSRealm {
async function createIFrameRealm(parent: Node): Promise<JSRealm> {
const iframe = document.createElement('iframe');
const iframeReadyPromise = new Promise<void>((resolve) => {
const listener = (event: MessageEvent) => {
if (
event.data === 'lynx:mtsready' && event.source === iframe.contentWindow
) {
resolve();
globalThis.removeEventListener('message', listener);
}
};
globalThis.addEventListener('message', listener);
});
iframe.style.display = 'none';
iframe.srcdoc =
'<!DOCTYPE html><html><head></head><body style="display:none"></body></html>';
'<!DOCTYPE html><html><head><script>parent.postMessage("lynx:mtsready","*")</script></head><body style="display:none"></body></html>';
iframe.sandbox = 'allow-same-origin allow-scripts'; // Restrict capabilities for security
iframe.loading = 'eager';
parent.appendChild(iframe);
await iframeReadyPromise;
const iframeWindow = iframe.contentWindow! as unknown as typeof globalThis;
Comment thread
PupilTong marked this conversation as resolved.
const loadScript: (url: string) => Promise<unknown> = async (url) => {
const script = iframe.contentDocument!.createElement('script');
script.fetchPriority = 'high';
script.defer = true;
script.async = false;
if (!iframe.contentDocument!.head) {
await new Promise<void>((resolve) => {
iframe.onload = () => resolve();
// In case iframe is already loaded, wait a macro task
setTimeout(() => resolve(), 0);
});
}
iframe.contentDocument!.head.appendChild(script);
return new Promise(async (resolve, reject) => {
script.onload = () => {
Expand Down Expand Up @@ -123,9 +127,6 @@ export function createRenderAllOnUI(
const i18nResources = new I18nResources();
const { exposureChangedCallback } = createExposureMonitor(shadowRoot);
const mtsRealm = createIFrameRealm(shadowRoot);
const mtsGlobalThis = mtsRealm.globalWindow as
& typeof globalThis
& MainThreadGlobalThis;
const { startMainThread, handleUpdatedData } = prepareMainThreadAPIs(
mainToBackgroundRpc,
shadowRoot,
Expand All @@ -144,9 +145,6 @@ export function createRenderAllOnUI(
},
loadTemplate,
);
const pendingUpdateCalls: Parameters<
RpcCallType<typeof updateDataEndpoint>
>[] = [];

const start = async (configs: StartMainThreadContextConfig) => {
if (ssrDumpInfo) {
Expand Down Expand Up @@ -192,26 +190,15 @@ export function createRenderAllOnUI(
} else {
await startMainThread(configs);
}

// Process any pending update calls that were queued while mtsGlobalThis was undefined
for (const [newData, options] of pendingUpdateCalls) {
handleUpdatedData(newData, options);
}
pendingUpdateCalls.length = 0;
};
const updateDataMainThread: RpcCallType<typeof updateDataEndpoint> = async (
newData,
options,
) => {
if (mtsGlobalThis) {
return handleUpdatedData(
newData,
options,
);
} else {
// Cache the call if mtsGlobalThis is not yet initialized
pendingUpdateCalls.push([newData, options]);
}
return handleUpdatedData(
newData,
options,
);
};
const updateI18nResourcesMainThread = (data: Cloneable) => {
i18nResources.setData(data as InitI18nResources);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function prepareMainThreadAPIs(
backgroundThreadRpc: Rpc,
rootDom: Document | ShadowRoot,
document: Document,
mtsRealm: JSRealm,
mtsRealmPromise: JSRealm | Promise<JSRealm>,
commitDocument: (
exposureChangedElements: HTMLElement[],
) => Promise<void> | void,
Expand Down Expand Up @@ -97,6 +97,7 @@ export function prepareMainThreadAPIs(
customSections,
cardType,
} = template;
const mtsRealm = await mtsRealmPromise;
markTimingInternal('decode_start');
await initWasmPromise;
const jsContext = new LynxCrossThreadContext({
Expand Down Expand Up @@ -254,10 +255,11 @@ export function prepareMainThreadAPIs(
await mtsRealm.loadScript(template.lepusCode.root);
jsContext.__start(); // start the jsContext after the runtime is created
}
function handleUpdatedData(
async function handleUpdatedData(
newData: Cloneable,
options: UpdateDataOptions | undefined,
) {
const mtsRealm = await mtsRealmPromise;
const runtime = mtsRealm.globalWindow as
& typeof globalThis
& MainThreadGlobalThis;
Expand Down
9 changes: 3 additions & 6 deletions packages/web-platform/web-tests/tests/react.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
ReturnType<typeof expect<Page>>['toHaveScreenshot']
>[0],
) => {
await expect(page).toHaveScreenshot([

Check failure on line 26 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (2/4) / check

[chromium] › tests/react.spec.ts:2354:7 › reactlynx3 tests › elements › image › basic-element-image-auto-size-with-padding

4) [chromium] › tests/react.spec.ts:2354:7 › reactlynx3 tests › elements › image › basic-element-image-auto-size-with-padding Error: expect(page).toHaveScreenshot(expected) failed Expected an image 393px by 802px, received 393px by 727px. 31216 pixels (ratio 0.10 of all image pixels) are different. Snapshot: image/auto-size-with-padding/index.png Call log: - Expect "toHaveScreenshot(image/auto-size-with-padding/index.png)" with timeout 5000ms - verifying given screenshot expectation - taking page screenshot - waiting for fonts to load... - fonts loaded - Expected an image 393px by 802px, received 393px by 727px. 31216 pixels (ratio 0.10 of all image pixels) are different. - waiting 100ms before taking screenshot - taking page screenshot - waiting for fonts to load... - fonts loaded - captured a stable screenshot - Expected an image 393px by 802px, received 393px by 727px. 31216 pixels (ratio 0.10 of all image pixels) are different. 24 | >[0], 25 | ) => { > 26 | await expect(page).toHaveScreenshot([ | ^ 27 | `${caseName}`, 28 | `${subcaseName}`, 29 | `${label}.png`, at diffScreenShot (/__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:26:22) at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:2358:17
`${caseName}`,
`${subcaseName}`,
`${label}.png`,
Expand Down Expand Up @@ -76,8 +76,7 @@
await expect(target).toHaveCSS('background-color', 'rgb(255, 192, 203)');
});

test('basic-reload', async ({ page, browserName }, { title }) => {
test.skip(browserName === 'webkit', 'playwright issue, tested locally');
test('basic-reload', async ({ page }, { title }) => {
await goto(page, title);
await wait(100);
const target = page.locator('#target');
Expand All @@ -91,8 +90,7 @@
await wait(100);
await expect(await target.getAttribute('style')).toContain('pink');
});
test('basic-reload-page-only-one', async ({ page, browserName }) => {
test.skip(browserName === 'webkit', 'playwright issue, tested locally');
test('basic-reload-page-only-one', async ({ page }) => {
await goto(page, 'basic-reload');
await wait(100);
await page.evaluate(() => {
Expand All @@ -107,7 +105,7 @@
)
.filter(i => i.getAttribute('lynx-tag') === 'page').length
),
).toBe(1);

Check failure on line 108 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (3/4) / check

[firefox] › tests/react.spec.ts:93:5 › reactlynx3 tests › basic › basic-reload-page-only-one

3) [firefox] › tests/react.spec.ts:93:5 › reactlynx3 tests › basic › basic-reload-page-only-one ── Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 1 Received: 0 106 | .filter(i => i.getAttribute('lynx-tag') === 'page').length 107 | ), > 108 | ).toBe(1); | ^ 109 | }); 110 | test('basic-bindtap', async ({ page }, { title }) => { 111 | await goto(page, title); at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:108:9

Check failure on line 108 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (3/4) / check

[firefox] › tests/react.spec.ts:93:5 › reactlynx3 tests › basic › basic-reload-page-only-one

3) [firefox] › tests/react.spec.ts:93:5 › reactlynx3 tests › basic › basic-reload-page-only-one ── Error: expect(received).toBe(expected) // Object.is equality Expected: 1 Received: 0 106 | .filter(i => i.getAttribute('lynx-tag') === 'page').length 107 | ), > 108 | ).toBe(1); | ^ 109 | }); 110 | test('basic-bindtap', async ({ page }, { title }) => { 111 | await goto(page, title); at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:108:9

Check failure on line 108 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright ALL_ON_UI-CSR (2/4) / check

[chromium] › tests/react.spec.ts:93:5 › reactlynx3 tests › basic › basic-reload-page-only-one

5) [chromium] › tests/react.spec.ts:93:5 › reactlynx3 tests › basic › basic-reload-page-only-one ─ Error: expect(received).toBe(expected) // Object.is equality Expected: 1 Received: 0 106 | .filter(i => i.getAttribute('lynx-tag') === 'page').length 107 | ), > 108 | ).toBe(1); | ^ 109 | }); 110 | test('basic-bindtap', async ({ page }, { title }) => { 111 | await goto(page, title); at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:108:9
});
test('basic-bindtap', async ({ page }, { title }) => {
await goto(page, title);
Expand Down Expand Up @@ -175,8 +173,7 @@
'green',
);
});
test('basic-globalProps-reload', async ({ page, browserName }, {}) => {
test.skip(browserName === 'webkit', 'playwright issue, tested locally');
test('basic-globalProps-reload', async ({ page }, {}) => {
await goto(page, 'basic-globalProps');
await wait(100);
expect(await page.locator('#target').getAttribute('style')).toContain(
Expand Down Expand Up @@ -1757,7 +1754,7 @@
});
await goto(page, title);
await wait(200);
!isSSR && expect(mts).toBe(true);

Check failure on line 1757 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (2/4) / check

[chromium] › tests/react.spec.ts:1742:5 › reactlynx3 tests › apis › api-global-disallowed-vars

1) [chromium] › tests/react.spec.ts:1742:5 › reactlynx3 tests › apis › api-global-disallowed-vars Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: true Received: false 1755 | await goto(page, title); 1756 | await wait(200); > 1757 | !isSSR && expect(mts).toBe(true); | ^ 1758 | expect(bts).toBe(true); 1759 | }, 1760 | ); at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:1757:31
expect(bts).toBe(true);
},
);
Expand All @@ -1776,7 +1773,7 @@
});
await goto(page, title);
await wait(200);
!isSSR && expect(mts).toBe(true);

Check failure on line 1776 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (2/4) / check

[chromium] › tests/react.spec.ts:1761:5 › reactlynx3 tests › apis › api-globalThis

2) [chromium] › tests/react.spec.ts:1761:5 › reactlynx3 tests › apis › api-globalThis ──────────── Error: expect(received).toBe(expected) // Object.is equality Expected: true Received: false 1774 | await goto(page, title); 1775 | await wait(200); > 1776 | !isSSR && expect(mts).toBe(true); | ^ 1777 | expect(bts).toBe(true); 1778 | }, 1779 | ); at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:1776:31
expect(bts).toBe(true);
},
);
Expand Down Expand Up @@ -2287,7 +2284,7 @@
await wait(300);
// --initialtextinitial
let count = await page.getByText('--').count();
expect(count).toBe(1);

Check failure on line 2287 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (2/4) / check

[chromium] › tests/react.spec.ts:2280:7 › reactlynx3 tests › elements › text › basic-element-text-set-native-props-with-setData

3) [chromium] › tests/react.spec.ts:2280:7 › reactlynx3 tests › elements › text › basic-element-text-set-native-props-with-setData Error: expect(received).toBe(expected) // Object.is equality Expected: 1 Received: 0 2285 | // --initialtextinitial 2286 | let count = await page.getByText('--').count(); > 2287 | expect(count).toBe(1); | ^ 2288 | count = await page.getByText('initial').count(); 2289 | expect(count).toBeGreaterThanOrEqual(1); 2290 | await page.locator('#target').click(); at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:2287:25
count = await page.getByText('initial').count();
expect(count).toBeGreaterThanOrEqual(1);
await page.locator('#target').click();
Expand Down Expand Up @@ -2770,7 +2767,7 @@
});
await wait(1000);
const lastEvent = eventDetails.pop();
expect(lastEvent.index).toBe(1);

Check failure on line 2770 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (2/4) / check

[chromium] › tests/react.spec.ts:2749:7 › reactlynx3 tests › elements › x-viewpager-ng › basic-element-x-viewpager-ng-bindchange

6) [chromium] › tests/react.spec.ts:2749:7 › reactlynx3 tests › elements › x-viewpager-ng › basic-element-x-viewpager-ng-bindchange Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 1 Received: undefined 2768 | await wait(1000); 2769 | const lastEvent = eventDetails.pop(); > 2770 | expect(lastEvent.index).toBe(1); | ^ 2771 | expect(lastEvent.isDragged).not.toBe(undefined); 2772 | }, 2773 | ); at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:2770:35

Check failure on line 2770 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (2/4) / check

[chromium] › tests/react.spec.ts:2749:7 › reactlynx3 tests › elements › x-viewpager-ng › basic-element-x-viewpager-ng-bindchange

6) [chromium] › tests/react.spec.ts:2749:7 › reactlynx3 tests › elements › x-viewpager-ng › basic-element-x-viewpager-ng-bindchange Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(received).toBe(expected) // Object.is equality Expected: 1 Received: undefined 2768 | await wait(1000); 2769 | const lastEvent = eventDetails.pop(); > 2770 | expect(lastEvent.index).toBe(1); | ^ 2771 | expect(lastEvent.isDragged).not.toBe(undefined); 2772 | }, 2773 | ); at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:2770:35

Check failure on line 2770 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (2/4) / check

[chromium] › tests/react.spec.ts:2749:7 › reactlynx3 tests › elements › x-viewpager-ng › basic-element-x-viewpager-ng-bindchange

6) [chromium] › tests/react.spec.ts:2749:7 › reactlynx3 tests › elements › x-viewpager-ng › basic-element-x-viewpager-ng-bindchange Error: expect(received).toBe(expected) // Object.is equality Expected: 1 Received: undefined 2768 | await wait(1000); 2769 | const lastEvent = eventDetails.pop(); > 2770 | expect(lastEvent.index).toBe(1); | ^ 2771 | expect(lastEvent.isDragged).not.toBe(undefined); 2772 | }, 2773 | ); at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:2770:35
expect(lastEvent.isDragged).not.toBe(undefined);
},
);
Expand Down Expand Up @@ -3041,7 +3038,7 @@
)?.click();
});
await wait(200);
expect(val).toBe(true);

Check failure on line 3041 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (2/4) / check

[chromium] › tests/react.spec.ts:3007:7 › reactlynx3 tests › elements › x-input › basic-element-x-input-getValue

7) [chromium] › tests/react.spec.ts:3007:7 › reactlynx3 tests › elements › x-input › basic-element-x-input-getValue Error: expect(received).toBe(expected) // Object.is equality Expected: true Received: false 3039 | }); 3040 | await wait(200); > 3041 | expect(val).toBe(true); | ^ 3042 | expect(selectionBegin).toBe(true); 3043 | expect(selectionEnd).toBe(true); 3044 | }, at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:3041:23
expect(selectionBegin).toBe(true);
expect(selectionEnd).toBe(true);
},
Expand All @@ -3058,7 +3055,7 @@
inputDom?.setSelectionRange(2, 5);
});
const result = await page.locator('.result').first().innerText();
expect(result).toBe('2-5');

Check failure on line 3058 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (1/4) / check

[webkit] › tests/react.spec.ts:3046:7 › reactlynx3 tests › elements › x-input › basic-element-x-input-bindselection

2) [webkit] › tests/react.spec.ts:3046:7 › reactlynx3 tests › elements › x-input › basic-element-x-input-bindselection Error: expect(received).toBe(expected) // Object.is equality Expected: "2-5" Received: "" 3056 | }); 3057 | const result = await page.locator('.result').first().innerText(); > 3058 | expect(result).toBe('2-5'); | ^ 3059 | }, 3060 | ); 3061 | test( at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:3058:26

Check failure on line 3058 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (2/4) / check

[chromium] › tests/react.spec.ts:3046:7 › reactlynx3 tests › elements › x-input › basic-element-x-input-bindselection

8) [chromium] › tests/react.spec.ts:3046:7 › reactlynx3 tests › elements › x-input › basic-element-x-input-bindselection Error: expect(received).toBe(expected) // Object.is equality Expected: "2-5" Received: "" 3056 | }); 3057 | const result = await page.locator('.result').first().innerText(); > 3058 | expect(result).toBe('2-5'); | ^ 3059 | }, 3060 | ); 3061 | test( at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:3058:26
},
);
test(
Expand Down Expand Up @@ -4428,7 +4425,7 @@
textareaDom?.setSelectionRange(2, 5);
});
const result = await page.locator('.result').first().innerText();
expect(result).toBe('2-5');

Check failure on line 4428 in packages/web-platform/web-tests/tests/react.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright MULTI_THREAD-CSR (1/4) / check

[webkit] › tests/react.spec.ts:4414:7 › reactlynx3 tests › elements › x-textarea › basic-element-x-textarea-bindselection

3) [webkit] › tests/react.spec.ts:4414:7 › reactlynx3 tests › elements › x-textarea › basic-element-x-textarea-bindselection Error: expect(received).toBe(expected) // Object.is equality Expected: "2-5" Received: "" 4426 | }); 4427 | const result = await page.locator('.result').first().innerText(); > 4428 | expect(result).toBe('2-5'); | ^ 4429 | }, 4430 | ); 4431 | at /__w/lynx-stack/lynx-stack/packages/web-platform/web-tests/tests/react.spec.ts:4428:26
},
);

Expand Down
Loading