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
15 changes: 15 additions & 0 deletions .changeset/giant-seas-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@lynx-js/web-mainthread-apis": patch
"@lynx-js/web-worker-runtime": patch
"@lynx-js/web-core": patch
---

feat: support thread strategy `all-on-ui`

```html
<lynx-view thread-strategy="all-on-ui"></lynx-view>
```

This will make the lynx's main-thread run on the UA's main thread.

Note that the `all-on-ui` does not support the HMR & chunk splitting yet.
5 changes: 5 additions & 0 deletions .changeset/slow-tools-ring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/offscreen-document": patch
---

feat: support parentNode
16 changes: 16 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,21 @@ jobs:
export PLAYWRIGHT_JUNIT_OUTPUT_NAME=test-report.junit.xml
pnpm --filter @lynx-js/web-tests run test --reporter='github,dot,junit,html'
pnpm --filter @lynx-js/web-tests run coverage:ci
playwright-linux-all-on-ui:
needs: build
uses: ./.github/workflows/workflow-test.yml
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
runs-on: lynx-ubuntu-24.04-xlarge
is-web: true
codecov-flags: "e2e"
run: |
export NODE_OPTIONS="--max-old-space-size=32768"
export PLAYWRIGHT_JUNIT_OUTPUT_NAME=test-report.junit.xml
export ALL_ON_UI=true
pnpm --filter @lynx-js/web-tests run test --reporter='github,dot,junit,html'
pnpm --filter @lynx-js/web-tests run coverage:ci
lighthouse:
needs: build
uses: ./.github/workflows/workflow-test.yml
Expand Down Expand Up @@ -270,6 +285,7 @@ jobs:
needs:
- code-style-check
- playwright-linux
- playwright-linux-all-on-ui
- test-plugins
- test-publish
- test-react
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export class OffscreenNode extends EventTarget {
return this._parentElement;
}

get parentNode(): OffscreenNode | null {
return this._parentElement;
}

get firstElementChild(): OffscreenNode | null {
return this._children[0] ?? null;
}
Expand Down
1 change: 1 addition & 0 deletions packages/web-platform/web-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"dependencies": {
"@lynx-js/offscreen-document": "workspace:*",
"@lynx-js/web-constants": "workspace:*",
"@lynx-js/web-mainthread-apis": "workspace:*",
"@lynx-js/web-worker-rpc": "workspace:*",
"@lynx-js/web-worker-runtime": "workspace:*"
},
Expand Down
21 changes: 21 additions & 0 deletions packages/web-platform/web-core/src/apis/LynxView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type INapiModulesCall = (
* @property {INapiModulesCall} onNapiModulesCall [optional] the NapiModule value handler.
* @property {"false" | "true" | null} injectHeadLinks [optional] @default true set it to "false" to disable injecting the <link href="" ref="stylesheet"> styles into shadowroot
* @property {number} lynxGroupId [optional] (attribute: "lynx-group-id") the background shared context id, which is used to share webworker between different lynx cards
* @property {"all-on-ui" | "multi-thread"} threadStrategy [optional] @default "multi-thread" (attribute: "thread-strategy") controls the thread strategy for current lynx view
* @property {(string)=>Promise<LynxTemplate>} customTemplateLoader [optional] the custom template loader, which is used to load the template
*
* @event error lynx card fired an error
Expand Down Expand Up @@ -284,6 +285,22 @@ export class LynxView extends HTMLElement {
}
}

/**
* @param
* @property
*/
get threadStrategy(): 'all-on-ui' | 'multi-thread' {
// @ts-expect-error
return this.getAttribute('thread-strategy');
}
set threadStrategy(val: 'all-on-ui' | 'multi-thread') {
if (val) {
this.setAttribute('thread-strategy', val);
} else {
this.removeAttribute('thread-strategy');
}
}

/**
* @private
*/
Expand Down Expand Up @@ -332,7 +349,11 @@ export class LynxView extends HTMLElement {
this.attachShadow({ mode: 'open' });
}
const lynxGroupId = this.lynxGroupId;
const threadStrategy = (this.threadStrategy ?? 'multi-thread') as
| 'all-on-ui'
| 'multi-thread';
const lynxView = createLynxView({
threadStrategy,
tagMap,
shadowRoot: this.shadowRoot!,
templateUrl: this.#url,
Expand Down
10 changes: 8 additions & 2 deletions packages/web-platform/web-core/src/apis/createLynxView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@ import type {
sendGlobalEventEndpoint,
UpdateDataType,
} from '@lynx-js/web-constants';
import { startUIThread } from '../uiThread/startUIThread.js';
import {
startUIThread,
type StartUIThreadCallbacks,
} from '../uiThread/startUIThread.js';
import type { RpcCallType } from '@lynx-js/web-worker-rpc';

export interface LynxViewConfigs {
templateUrl: string;
initData: Cloneable;
globalProps: Cloneable;
shadowRoot: ShadowRoot;
callbacks: Parameters<typeof startUIThread>[4];
callbacks: StartUIThreadCallbacks;
nativeModulesMap: NativeModulesMap;
napiModulesMap: NapiModulesMap;
tagMap: Record<string, string>;
lynxGroupId: number | undefined;
threadStrategy: 'all-on-ui' | 'multi-thread';
Comment thread
PupilTong marked this conversation as resolved.
}

export interface LynxView {
Expand All @@ -45,6 +49,7 @@ export function createLynxView(configs: LynxViewConfigs): LynxView {
napiModulesMap,
tagMap,
lynxGroupId,
threadStrategy = 'multi-thread',
} = configs;
return startUIThread(
templateUrl,
Expand All @@ -60,6 +65,7 @@ export function createLynxView(configs: LynxViewConfigs): LynxView {
},
shadowRoot,
lynxGroupId,
threadStrategy,
callbacks,
);
}
30 changes: 25 additions & 5 deletions packages/web-platform/web-core/src/uiThread/bootWorkers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,21 @@ const backgroundWorkerContextCount: number[] = [];
const contextIdToBackgroundWorker: (Worker | undefined)[] = [];

let preHeatedMainWorker = createMainWorker();

export function bootWorkers(
lynxGroupId: number | undefined,
allOnUI?: boolean,
): LynxViewRpc {
const curMainWorker = preHeatedMainWorker;
preHeatedMainWorker = createMainWorker();
let curMainWorker: {
mainThreadRpc: Rpc;
mainThreadWorker?: Worker;
channelMainThreadWithBackground: MessageChannel;
};
if (allOnUI) {
curMainWorker = createUIChannel();
} else {
curMainWorker = preHeatedMainWorker;
preHeatedMainWorker = createMainWorker();
}
const curBackgroundWorker = createBackgroundWorker(
lynxGroupId,
curMainWorker.channelMainThreadWithBackground,
Expand All @@ -32,12 +41,11 @@ export function bootWorkers(
backgroundWorkerContextCount[lynxGroupId] = 1;
}
}

return {
mainThreadRpc: curMainWorker.mainThreadRpc,
backgroundRpc: curBackgroundWorker.backgroundRpc,
terminateWorkers: () => {
curMainWorker.mainThreadWorker.terminate();
curMainWorker.mainThreadWorker?.terminate();
if (lynxGroupId === undefined) {
curBackgroundWorker.backgroundThreadWorker.terminate();
} else if (backgroundWorkerContextCount[lynxGroupId] === 1) {
Expand All @@ -49,6 +57,18 @@ export function bootWorkers(
};
}

function createUIChannel() {
const channelMainThreadWithBackground = new MessageChannel();
const mainThreadRpc = new Rpc(
channelMainThreadWithBackground.port1,
'main-to-bg',
);
return {
mainThreadRpc,
channelMainThreadWithBackground,
};
}

function createMainWorker() {
const channelToMainThread = new MessageChannel();
const channelMainThreadWithBackground = new MessageChannel();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type {
MainThreadStartConfigs,
RpcCallType,
updateDataEndpoint,
} from '@lynx-js/web-constants';
import type { MainThreadRuntime } from '@lynx-js/web-mainthread-apis';
import { Rpc } from '@lynx-js/web-worker-rpc';

const {
loadMainThread,
} = await import('@lynx-js/web-mainthread-apis');

export function createRenderAllOnUI(
mainToBackgroundRpc: Rpc,
shadowRoot: ShadowRoot,
markTimingInternal: (
timingKey: string,
pipelineId?: string,
timeStamp?: number,
) => void,
callbacks: {
onError?: () => void;
},
) {
if (!globalThis.module) {
Object.assign(globalThis, { module: {} });
}
const docu = Object.assign(shadowRoot, {
createElement: document.createElement.bind(document),
});
const { startMainThread } = loadMainThread(
mainToBackgroundRpc,
docu,
() => {},
markTimingInternal,
() => {
callbacks.onError?.();
},
);
let runtime!: MainThreadRuntime;
const start = async (configs: MainThreadStartConfigs) => {
const mainThreadRuntime = startMainThread(configs);
runtime = await mainThreadRuntime;
};
const updateDataMainThread: RpcCallType<typeof updateDataEndpoint> = async (
...args
) => {
runtime.updatePage?.(...args);
};
return {
start,
updateDataMainThread,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
mainThreadStartEndpoint,
updateDataEndpoint,
} from '@lynx-js/web-constants';
import type { Rpc } from '@lynx-js/web-worker-rpc';
import { registerReportErrorHandler } from './crossThreadHandlers/registerReportErrorHandler.js';
import { registerFlushElementTreeHandler } from './crossThreadHandlers/registerFlushElementTreeHandler.js';

export function createRenderMultiThread(
mainThreadRpc: Rpc,
shadowRoot: ShadowRoot,
callbacks: {
onError?: () => void;
},
) {
registerReportErrorHandler(
mainThreadRpc,
callbacks.onError,
);
registerFlushElementTreeHandler(
mainThreadRpc,
{
shadowRoot,
},
);
const start = mainThreadRpc.createCall(mainThreadStartEndpoint);
const updateDataMainThread = mainThreadRpc.createCall(updateDataEndpoint);
return {
start,
updateDataMainThread,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// 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 { Rpc } from '@lynx-js/web-worker-rpc';
import type { RpcCallType } from '@lynx-js/web-worker-rpc';
import type { LynxView } from '../../apis/createLynxView.js';
import {
updateDataEndpoint,
Expand All @@ -11,8 +11,8 @@ import {
} from '@lynx-js/web-constants';

export function createUpdateData(
mainThreadRpc: Rpc,
backgroundRpc: Rpc,
updateDataMainThread: RpcCallType<typeof updateDataEndpoint>,
updateDataBackground: RpcCallType<typeof updateDataEndpoint>,
): LynxView['updateData'] {
return (
data: Cloneable,
Expand All @@ -21,8 +21,8 @@ export function createUpdateData(
) => {
Promise.all([
// There is no need to process options for now, as they have default values.
mainThreadRpc.invoke(updateDataEndpoint, [data, {}]),
backgroundRpc.invoke(updateDataEndpoint, [data, {}]),
updateDataMainThread(data, {}),
updateDataBackground(data, {}),
]).then(() => callback?.());
};
}
59 changes: 59 additions & 0 deletions packages/web-platform/web-core/src/uiThread/startBackground.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
markTimingEndpoint,
sendGlobalEventEndpoint,
updateDataEndpoint,
type NapiModulesCall,
type NativeModulesCall,
} from '@lynx-js/web-constants';
import type { Rpc } from '@lynx-js/web-worker-rpc';
import { registerInvokeUIMethodHandler } from './crossThreadHandlers/registerInvokeUIMethodHandler.js';
import { registerNativePropsHandler } from './crossThreadHandlers/registerSetNativePropsHandler.js';
import { registerNativeModulesCallHandler } from './crossThreadHandlers/registerNativeModulesCallHandler.js';
import { registerTriggerComponentEventHandler } from './crossThreadHandlers/registerTriggerComponentEventHandler.js';
import { registerSelectComponentHandler } from './crossThreadHandlers/registerSelectComponentHandler.js';
import { registerNapiModulesCallHandler } from './crossThreadHandlers/registerNapiModulesCallHandler.js';
import { registerDispatchLynxViewEventHandler } from './crossThreadHandlers/registerDispatchLynxViewEventHandler.js';

export function startBackground(
backgroundRpc: Rpc,
shadowRoot: ShadowRoot,
callbacks: {
nativeModulesCall: NativeModulesCall;
napiModulesCall: NapiModulesCall;
},
) {
registerInvokeUIMethodHandler(
backgroundRpc,
shadowRoot,
);
registerNativePropsHandler(
backgroundRpc,
shadowRoot,
);
registerTriggerComponentEventHandler(
backgroundRpc,
shadowRoot,
);
registerSelectComponentHandler(
backgroundRpc,
shadowRoot,
);
registerNativeModulesCallHandler(
backgroundRpc,
callbacks.nativeModulesCall,
);
registerNapiModulesCallHandler(
backgroundRpc,
callbacks.napiModulesCall,
);
registerDispatchLynxViewEventHandler(backgroundRpc, shadowRoot);

const sendGlobalEvent = backgroundRpc.createCall(sendGlobalEventEndpoint);
const markTiming = backgroundRpc.createCall(markTimingEndpoint);
const updateDataBackground = backgroundRpc.createCall(updateDataEndpoint);
return {
sendGlobalEvent,
markTiming,
updateDataBackground,
};
}
Loading
Loading