From 6e649f76295c271fa3903e41551fc0be55280063 Mon Sep 17 00:00:00 2001 From: pupiltong <12288479+PupilTong@users.noreply.github.com> Date: Wed, 15 Oct 2025 19:25:51 +0800 Subject: [PATCH 1/2] fix(web): resolve mts freeze issue after reload by ensuring iframe is fully loaded --- .changeset/loose-crews-slide.md | 10 ++++ .../src/uiThread/createRenderAllOnUI.ts | 47 +++++++------------ .../src/prepareMainThreadAPIs.ts | 6 ++- .../web-tests/tests/react.spec.ts | 9 ++-- 4 files changed, 33 insertions(+), 39 deletions(-) create mode 100644 .changeset/loose-crews-slide.md diff --git a/.changeset/loose-crews-slide.md b/.changeset/loose-crews-slide.md new file mode 100644 index 0000000000..3f2d7e5afd --- /dev/null +++ b/.changeset/loose-crews-slide.md @@ -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. diff --git a/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts b/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts index d0c104d4ea..18da314d3b 100644 --- a/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts +++ b/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts @@ -6,7 +6,6 @@ import { type StartMainThreadContextConfig, type RpcCallType, updateDataEndpoint, - type MainThreadGlobalThis, type I18nResourceTranslationOptions, type CloneableObject, i18nResourceMissedEventName, @@ -38,27 +37,30 @@ 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 { const iframe = document.createElement('iframe'); + const iframeReadyPromise = new Promise((resolve) => { + const listener = (event: MessageEvent) => { + if ((event as MessageEvent).data === 'lynx:mtsready') { + resolve(); + globalThis.removeEventListener('message', listener); + } + }; + globalThis.addEventListener('message', listener); + }); iframe.style.display = 'none'; iframe.srcdoc = - ''; + ''; 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; const loadScript: (url: string) => Promise = async (url) => { const script = iframe.contentDocument!.createElement('script'); script.fetchPriority = 'high'; script.defer = true; script.async = false; - if (!iframe.contentDocument!.head) { - await new Promise((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 = () => { @@ -123,9 +125,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, @@ -144,9 +143,6 @@ export function createRenderAllOnUI( }, loadTemplate, ); - const pendingUpdateCalls: Parameters< - RpcCallType - >[] = []; const start = async (configs: StartMainThreadContextConfig) => { if (ssrDumpInfo) { @@ -192,26 +188,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 = 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); diff --git a/packages/web-platform/web-mainthread-apis/src/prepareMainThreadAPIs.ts b/packages/web-platform/web-mainthread-apis/src/prepareMainThreadAPIs.ts index 722382885b..6c2412cdac 100644 --- a/packages/web-platform/web-mainthread-apis/src/prepareMainThreadAPIs.ts +++ b/packages/web-platform/web-mainthread-apis/src/prepareMainThreadAPIs.ts @@ -43,7 +43,7 @@ export function prepareMainThreadAPIs( backgroundThreadRpc: Rpc, rootDom: Document | ShadowRoot, document: Document, - mtsRealm: JSRealm, + mtsRealmPromise: JSRealm | Promise, commitDocument: ( exposureChangedElements: HTMLElement[], ) => Promise | void, @@ -97,6 +97,7 @@ export function prepareMainThreadAPIs( customSections, cardType, } = template; + const mtsRealm = await mtsRealmPromise; markTimingInternal('decode_start'); await initWasmPromise; const jsContext = new LynxCrossThreadContext({ @@ -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; diff --git a/packages/web-platform/web-tests/tests/react.spec.ts b/packages/web-platform/web-tests/tests/react.spec.ts index f6dcf5e8c2..9ce2603797 100644 --- a/packages/web-platform/web-tests/tests/react.spec.ts +++ b/packages/web-platform/web-tests/tests/react.spec.ts @@ -76,8 +76,7 @@ test.describe('reactlynx3 tests', () => { 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'); @@ -91,8 +90,7 @@ test.describe('reactlynx3 tests', () => { 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(() => { @@ -175,8 +173,7 @@ test.describe('reactlynx3 tests', () => { '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( From 8e7dc2028a3cdc0a95d48dcbcf9be5abb762645c Mon Sep 17 00:00:00 2001 From: pupiltong <12288479+PupilTong@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:56:56 +0800 Subject: [PATCH 2/2] accept ai --- .../web-core/src/uiThread/createRenderAllOnUI.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts b/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts index 18da314d3b..5a3eb37342 100644 --- a/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts +++ b/packages/web-platform/web-core/src/uiThread/createRenderAllOnUI.ts @@ -41,7 +41,9 @@ async function createIFrameRealm(parent: Node): Promise { const iframe = document.createElement('iframe'); const iframeReadyPromise = new Promise((resolve) => { const listener = (event: MessageEvent) => { - if ((event as MessageEvent).data === 'lynx:mtsready') { + if ( + event.data === 'lynx:mtsready' && event.source === iframe.contentWindow + ) { resolve(); globalThis.removeEventListener('message', listener); } @@ -50,7 +52,7 @@ async function createIFrameRealm(parent: Node): Promise { }); iframe.style.display = 'none'; iframe.srcdoc = - ''; + ''; iframe.sandbox = 'allow-same-origin allow-scripts'; // Restrict capabilities for security iframe.loading = 'eager'; parent.appendChild(iframe);