diff --git a/.changeset/better-wings-peel.md b/.changeset/better-wings-peel.md new file mode 100644 index 0000000000..b9a734c5d3 --- /dev/null +++ b/.changeset/better-wings-peel.md @@ -0,0 +1,7 @@ +--- +"@lynx-js/web-worker-runtime": patch +"@lynx-js/web-constants": patch +"@lynx-js/web-core": patch +--- + +feat: support BTS API `lynx.reportError` && `__SetSourceMapRelease`, now you can use it and handle it in lynx-view error event. diff --git a/packages/web-platform/web-constants/src/types/NativeApp.ts b/packages/web-platform/web-constants/src/types/NativeApp.ts index 5f5735a4ed..99d91758c2 100644 --- a/packages/web-platform/web-constants/src/types/NativeApp.ts +++ b/packages/web-platform/web-constants/src/types/NativeApp.ts @@ -192,4 +192,7 @@ export interface NativeApp { getSharedData(dataKey: string): T | undefined; i18nResource: I18nResource; + + reportException: (error: Error, _: unknown) => void; + __SetSourceMapRelease: (err: Error) => void; } diff --git a/packages/web-platform/web-core/src/uiThread/startBackground.ts b/packages/web-platform/web-core/src/uiThread/startBackground.ts index cde3a13518..2b8ba16347 100644 --- a/packages/web-platform/web-core/src/uiThread/startBackground.ts +++ b/packages/web-platform/web-core/src/uiThread/startBackground.ts @@ -7,8 +7,6 @@ import { type Cloneable, type I18nResourceTranslationOptions, type InitI18nResources, - type NapiModulesCall, - type NativeModulesCall, } from '@lynx-js/web-constants'; import type { Rpc } from '@lynx-js/web-worker-rpc'; import { registerInvokeUIMethodHandler } from './crossThreadHandlers/registerInvokeUIMethodHandler.js'; @@ -19,14 +17,13 @@ import { registerSelectComponentHandler } from './crossThreadHandlers/registerSe import { registerNapiModulesCallHandler } from './crossThreadHandlers/registerNapiModulesCallHandler.js'; import { registerDispatchLynxViewEventHandler } from './crossThreadHandlers/registerDispatchLynxViewEventHandler.js'; import { registerTriggerElementMethodEndpointHandler } from './crossThreadHandlers/registerTriggerElementMethodEndpointHandler.js'; +import type { StartUIThreadCallbacks } from './startUIThread.js'; +import { registerReportErrorHandler } from './crossThreadHandlers/registerReportErrorHandler.js'; export function startBackground( backgroundRpc: Rpc, shadowRoot: ShadowRoot, - callbacks: { - nativeModulesCall: NativeModulesCall; - napiModulesCall: NapiModulesCall; - }, + callbacks: StartUIThreadCallbacks, ) { registerInvokeUIMethodHandler( backgroundRpc, @@ -54,6 +51,7 @@ export function startBackground( ); registerDispatchLynxViewEventHandler(backgroundRpc, shadowRoot); registerTriggerElementMethodEndpointHandler(backgroundRpc, shadowRoot); + registerReportErrorHandler(backgroundRpc, callbacks.onError); const sendGlobalEvent = backgroundRpc.createCall(sendGlobalEventEndpoint); const markTiming = backgroundRpc.createCall(markTimingEndpoint); diff --git a/packages/web-platform/web-tests/shell-project/index.ts b/packages/web-platform/web-tests/shell-project/index.ts index f5b15fc7c2..cb443f8cfc 100644 --- a/packages/web-platform/web-tests/shell-project/index.ts +++ b/packages/web-platform/web-tests/shell-project/index.ts @@ -80,6 +80,19 @@ if (casename) { return template; }; } + lynxView.addEventListener('error', (e) => { + console.log(e); + // these two issues have been added to the fix plan and skip for now. + if ( + ![ + 'config-mode-dev-with-all-in-one', + 'basic-element-x-textarea-input-filter', + ].includes(casename) + ) { + lynxView.setAttribute('style', 'display:none'); + lynxView.innerHTML = ''; + } + }); }); if (casename2) { lynxViewTests(lynxView2 => { diff --git a/packages/web-platform/web-tests/shell-project/lynx-view.ts b/packages/web-platform/web-tests/shell-project/lynx-view.ts index f473fe9a69..c9d98dcc9d 100644 --- a/packages/web-platform/web-tests/shell-project/lynx-view.ts +++ b/packages/web-platform/web-tests/shell-project/lynx-view.ts @@ -14,11 +14,6 @@ export const lynxViewTests = (callback: (lynxView: LynxView) => void) => { lynxView.initData = { mockData: 'mockData' }; lynxView.setAttribute('height', 'auto'); lynxView.globalProps = { backgroundColor: 'pink' }; - lynxView.addEventListener('error', (e) => { - console.log(e); - lynxView.setAttribute('style', 'display:none'); - lynxView.innerHTML = ''; - }); lynxView.addEventListener('timing', (ev) => { // @ts-expect-error globalThis.timing = Object.assign(globalThis.timing ?? {}, ev.detail); diff --git a/packages/web-platform/web-tests/tests/react.spec.ts b/packages/web-platform/web-tests/tests/react.spec.ts index 94349a318f..3038215d65 100644 --- a/packages/web-platform/web-tests/tests/react.spec.ts +++ b/packages/web-platform/web-tests/tests/react.spec.ts @@ -711,10 +711,11 @@ test.describe('reactlynx3 tests', () => { const event = await msg.args()[0]?.evaluate((e) => { return { type: e.type, + message: e.detail?.error?.message, release: e.detail?.release, }; }); - if (!event || event.type !== 'error') { + if (!event || event.type !== 'error' || event.message !== 'error') { return; } if ( @@ -727,6 +728,63 @@ test.describe('reactlynx3 tests', () => { await wait(500); expect(success).toBe(true); }); + test('api-set-release-bts', async ({ page }, { title }) => { + let success = false; + await page.on('console', async (msg) => { + const event = await msg.args()[0]?.evaluate((e) => { + return { + type: e.type, + message: e.detail?.error?.message, + release: e.detail?.release, + }; + }); + if ( + !event || event.type !== 'error' + || event.message !== 'loadCard failed Error: error' + ) { + return; + } + if ( + typeof event.release === 'string' && event.release === '111' + ) { + success = true; + } + }); + await goto(page, title); + await wait(500); + expect(success).toBe(true); + }); + test('api-report-error', async ({ page }, { title }) => { + let offset = false; + await page.on('console', async (msg) => { + const event = await msg.args()[0]?.evaluate((e) => { + return { + type: e.type, + error: e.detail?.error, + offset: e.detail?.sourceMap?.offset, + }; + }); + if (!event || event.type !== 'error') { + return; + } + if ( + typeof event.offset.line === 'number' && event.offset.line === 2 + && typeof event.offset.col === 'number' && event.offset.col === 0 + && event.error.message === 'Error: foo' + && typeof event.error.stack === 'string' + && event.error.stack !== '' + ) { + offset = true; + } + }); + await goto(page, title); + await wait(200); + await page.locator('#target').click(); + await wait(500); + const target = await page.locator('lynx-view'); + await expect(target).toHaveCSS('display', 'none'); + await expect(offset).toBe(true); + }); test('api-preheat', async ({ page }, { title }) => { await goto(page, title); diff --git a/packages/web-platform/web-tests/tests/react/api-report-error/index.jsx b/packages/web-platform/web-tests/tests/react/api-report-error/index.jsx new file mode 100644 index 0000000000..0e10017d46 --- /dev/null +++ b/packages/web-platform/web-tests/tests/react/api-report-error/index.jsx @@ -0,0 +1,24 @@ +// Copyright 2023 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 { root, useEffect, useState } from '@lynx-js/react'; + +function App() { + const handleTap = () => { + lynx.reportError('foo'); + }; + + return ( + + ); +} + +root.render(); diff --git a/packages/web-platform/web-tests/tests/react/api-set-release-bts/index.jsx b/packages/web-platform/web-tests/tests/react/api-set-release-bts/index.jsx new file mode 100644 index 0000000000..576af38a13 --- /dev/null +++ b/packages/web-platform/web-tests/tests/react/api-set-release-bts/index.jsx @@ -0,0 +1,15 @@ +// Copyright 2023 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 { root } from '@lynx-js/react'; + +function App() { + if (!__MAIN_THREAD__) { + const err = new Error('111'); + err.name = 'LynxGetSourceMapReleaseError'; + lynxCoreInject.tt.setSourceMapRelease(err); + } + throw new Error('error'); +} + +root.render(); diff --git a/packages/web-platform/web-worker-runtime/src/backgroundThread/background-apis/createNativeApp.ts b/packages/web-platform/web-worker-runtime/src/backgroundThread/background-apis/createNativeApp.ts index 872121110e..52c072bf5a 100644 --- a/packages/web-platform/web-worker-runtime/src/backgroundThread/background-apis/createNativeApp.ts +++ b/packages/web-platform/web-worker-runtime/src/backgroundThread/background-apis/createNativeApp.ts @@ -14,6 +14,7 @@ import { systemInfo, type BackMainThreadContextConfig, I18nResource, + reportErrorEndpoint, } from '@lynx-js/web-constants'; import { createInvokeUIMethod } from './crossThreadHandlers/createInvokeUIMethod.js'; import { registerPublicComponentEventHandler } from './crossThreadHandlers/registerPublicComponentEventHandler.js'; @@ -61,6 +62,7 @@ export async function createNativeApp( selectComponentEndpoint, 3, ); + const reportError = uiThreadRpc.createCall(reportErrorEndpoint); const createBundleInitReturnObj = (): BundleInitReturnObj => { const entry = (globalThis.module as LynxJSModule).exports; return { @@ -91,6 +93,7 @@ export async function createNativeApp( }; }; const i18nResource = new I18nResource(); + let release = ''; const nativeApp: NativeApp = { id: (nativeAppCount++).toString(), ...performanceApis, @@ -167,6 +170,8 @@ export async function createNativeApp( return sharedData[dataKey] as T | undefined; }, i18nResource, + reportException: (err: Error, _: unknown) => reportError(err, _, release), + __SetSourceMapRelease: (err: Error) => release = err.message, }; return nativeApp; }