@lynx-js/web-core
Version:
This is an internal experimental package, do not use
207 lines • 9.44 kB
JavaScript
/*
* Copyright (C) 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 { Rpc } from '@lynx-js/web-worker-rpc';
import { dispatchCoreContextOnBackgroundEndpoint, dispatchJSContextOnMainThreadEndpoint, disposeEndpoint, markTimingEndpoint, postTimingFlagsEndpoint, publicComponentEventEndpoint, publishEventEndpoint, sendGlobalEventEndpoint, dispatchI18nResourceEndpoint, updateDataEndpoint, updateGlobalPropsEndpoint, BackgroundThreadStartEndpoint, callLepusMethodEndpoint, switchExposureServiceEndpoint, reportErrorEndpoint, dispatchLynxViewEventEndpoint, updateBTSChunkEndpoint, queryComponentEndpoint, } from '../endpoints.js';
import { LynxCrossThreadContext } from '../LynxCrossThreadContext.js';
import {} from './LynxViewInstance.js';
import { registerInvokeUIMethodHandler } from './crossThreadHandlers/registerInvokeUIMethodHandler.js';
import { registerNativePropsHandler } from './crossThreadHandlers/registerSetNativePropsHandler.js';
import { registerGetPathInfoHandler } from './crossThreadHandlers/registerGetPathInfoHandler.js';
import { registerSelectComponentHandler } from './crossThreadHandlers/registerSelectComponentHandler.js';
import { registerTriggerComponentEventHandler } from './crossThreadHandlers/registerTriggerComponentEventHandler.js';
import { registerTriggerElementMethodEndpointHandler } from './crossThreadHandlers/registerTriggerElementMethodEndpointHandler.js';
import { registerNapiModulesCallHandler } from './crossThreadHandlers/registerNapiModulesCallHandler.js';
import { registerNativeModulesCallHandler } from './crossThreadHandlers/registerNativeModulesCallHandler.js';
import { registerReloadHandler } from './crossThreadHandlers/registerReloadHandler.js';
function createWebWorker() {
return new Worker(
/* webpackFetchPriority: "high" */
/* webpackChunkName: "web-core-worker-chunk" */
/* webpackPrefetch: true */
/* webpackPreload: true */
new URL('../background/index.js', import.meta.url), {
type: 'module',
name: 'lynx-bg',
});
}
export class BackgroundThread {
static contextIdToBackgroundWorker = [];
#rpc;
#webWorker;
#nextMacroTask = null;
#caughtTimingInfo = [];
#batchSendTimingInfo;
jsContext;
#messagePort;
postTimingFlags;
sendGlobalEvent;
publicComponentEvent;
publishEvent;
dispatchI18nResource;
updateData;
updateGlobalProps;
updateBTSChunk;
#lynxGroupId;
#lynxViewInstance;
#btsReady;
#btsReadyResolver;
#btsStarted = false;
constructor(lynxGroupId, lynxViewInstance) {
this.#lynxGroupId = lynxGroupId;
this.#lynxViewInstance = lynxViewInstance;
this.#rpc = new Rpc(undefined, 'main-to-bg');
this.jsContext = new LynxCrossThreadContext({
rpc: this.#rpc,
receiveEventEndpoint: dispatchJSContextOnMainThreadEndpoint,
sendEventEndpoint: dispatchCoreContextOnBackgroundEndpoint,
});
this.#btsReady = new Promise((resolve) => {
this.#btsReadyResolver = resolve;
});
this.jsContext.__start();
this.#batchSendTimingInfo = this.#rpc.createCall(markTimingEndpoint);
this.postTimingFlags = this.#rpc.createCall(postTimingFlagsEndpoint);
this.sendGlobalEvent = this.#rpc.createCall(sendGlobalEventEndpoint);
this.publicComponentEvent = this.#rpc.createCall(publicComponentEventEndpoint);
this.publishEvent = this.#rpc.createCall(publishEventEndpoint);
this.dispatchI18nResource = this.#rpc.createCall(dispatchI18nResourceEndpoint);
this.updateData = this.#rpc.createCall(updateDataEndpoint);
this.updateGlobalProps = this.#rpc.createCall(updateGlobalPropsEndpoint);
this.updateBTSChunk = this.#rpc.createCall(updateBTSChunkEndpoint);
}
startWebWorker(initData, globalProps, cardType, customSections, nativeModulesMap, napiModulesMap) {
if (this.#webWorker)
return;
// now start the background worker
if (this.#lynxGroupId !== undefined) {
const group = BackgroundThread.contextIdToBackgroundWorker[this.#lynxGroupId];
if (group) {
group.runningCards += 1;
}
else {
BackgroundThread.contextIdToBackgroundWorker[this.#lynxGroupId] = {
worker: createWebWorker(),
runningCards: 1,
};
}
this.#webWorker = BackgroundThread.contextIdToBackgroundWorker[this.#lynxGroupId].worker;
}
else {
this.#webWorker = createWebWorker();
}
const messageChannel = new MessageChannel();
this.#webWorker.postMessage({
mainThreadMessagePort: messageChannel.port2,
systemInfo: this.#lynxViewInstance.systemInfo,
initData,
globalProps,
cardType,
customSections,
nativeModulesMap,
napiModulesMap,
entryTemplateUrl: this.#lynxViewInstance.templateUrl,
}, [messageChannel.port2]);
this.#messagePort = messageChannel.port1;
this.#rpc.setMessagePort(messageChannel.port1);
}
startBTS() {
if (this.#btsStarted)
return;
this.#btsStarted = true;
// prepare bts rpc handlers
this.#rpc.registerHandler(callLepusMethodEndpoint, (methodName, data) => {
const method = this.#lynxViewInstance.mainThreadGlobalThis[methodName];
if (typeof method === 'function') {
method.call(this.#lynxViewInstance.mainThreadGlobalThis, data);
}
else {
console.error(`Method ${methodName} not found on mainThreadGlobalThis`);
}
});
this.#rpc.registerHandler(switchExposureServiceEndpoint, this.#lynxViewInstance.exposureServices.switchExposureService.bind(this.#lynxViewInstance.exposureServices));
this.#rpc.registerHandler(reportErrorEndpoint, (e, _, release) => {
this.#lynxViewInstance.reportError(e, release, 'app-service.js');
});
this.#rpc.registerHandler(dispatchLynxViewEventEndpoint, (eventType, detail) => {
this.#lynxViewInstance.rootDom.dispatchEvent(new CustomEvent(eventType, {
detail,
bubbles: true,
cancelable: true,
composed: true,
}));
});
this.#rpc.registerHandler(queryComponentEndpoint, (url) => {
return this.#lynxViewInstance.queryComponent(url).then(() => {
this.jsContext.dispatchEvent({
type: '__OnDynamicJSSourcePrepared',
data: url,
});
return {
code: 0,
detail: {
schema: url,
},
};
});
});
registerReloadHandler(this.#rpc, this.#lynxViewInstance);
registerGetPathInfoHandler(this.#rpc, this.#lynxViewInstance);
registerInvokeUIMethodHandler(this.#rpc, this.#lynxViewInstance);
registerNapiModulesCallHandler(this.#rpc, this.#lynxViewInstance);
registerNativeModulesCallHandler(this.#rpc, this.#lynxViewInstance);
registerSelectComponentHandler(this.#rpc, this.#lynxViewInstance);
registerNativePropsHandler(this.#rpc, this.#lynxViewInstance);
registerTriggerComponentEventHandler(this.#rpc, this.#lynxViewInstance);
registerTriggerElementMethodEndpointHandler(this.#rpc, this.#lynxViewInstance);
this.#rpc.invoke(BackgroundThreadStartEndpoint, []).then(this.#btsReadyResolver);
}
markTiming(timingKey, pipelineId, timeStamp) {
this.#caughtTimingInfo.push({
timingKey,
pipelineId,
timeStamp: timeStamp ?? (performance.now() + performance.timeOrigin),
});
if (this.#nextMacroTask === null) {
this.#nextMacroTask = setTimeout(() => {
this.flushTimingInfo();
}, 500);
}
}
/**
* Flush the timing info immediately.
*/
flushTimingInfo() {
this.#batchSendTimingInfo(this.#caughtTimingInfo);
this.#caughtTimingInfo = [];
if (this.#nextMacroTask !== null) {
clearTimeout(this.#nextMacroTask);
this.#nextMacroTask = null;
}
}
async [Symbol.asyncDispose]() {
if (this.#btsStarted) {
await this.#btsReady;
await this.#rpc.invoke(disposeEndpoint, []);
}
if (this.#lynxGroupId !== undefined) {
const group = BackgroundThread.contextIdToBackgroundWorker[this.#lynxGroupId];
if (group) {
group.runningCards -= 1;
if (group.runningCards === 0) {
group.worker.terminate();
BackgroundThread.contextIdToBackgroundWorker[this.#lynxGroupId] = undefined;
}
}
}
else {
this.#webWorker?.terminate();
}
this.#messagePort?.close();
this.#messagePort = undefined;
this.#nextMacroTask && clearTimeout(this.#nextMacroTask);
}
}
//# sourceMappingURL=Background.js.map