@lynx-js/web-core
Version:
This is an internal experimental package, do not use
201 lines • 8.7 kB
JavaScript
import { loadUnknownElementEventName, systemInfoBase, } from '../../constants.js';
import { BackgroundThread } from './Background.js';
import { I18nManager } from './I18n.js';
import { WASMJSBinding } from './elementAPIs/WASMJSBinding.js';
import { ExposureServices } from './ExposureServices.js';
import { createElementAPI } from './elementAPIs/createElementAPI.js';
import { createMainThreadGlobalAPIs } from './createMainThreadGlobalAPIs.js';
import { templateManager } from './TemplateManager.js';
import { loadAllWebElements } from '../webElementsDynamicLoader.js';
// @ts-expect-error
import IN_SHADOW_CSS_MODERN from '../../../css/in_shadow.css?inline';
import { requestIdleCallbackImpl } from './utils/requestIdleCallback.js';
loadAllWebElements().catch((e) => {
console.error('[lynx-web] Failed to load web elements', e);
});
const IN_SHADOW_CSS = URL.createObjectURL(new Blob([IN_SHADOW_CSS_MODERN], { type: 'text/css' }));
const linkElement = document.createElement('link');
linkElement.rel = 'stylesheet';
linkElement.href = IN_SHADOW_CSS;
linkElement.type = 'text/css';
linkElement.fetchPriority = 'high';
linkElement.blocking = 'render';
const pixelRatio = window.devicePixelRatio;
const screenWidth = window.screen.availWidth * pixelRatio;
const screenHeight = window.screen.availHeight * pixelRatio;
export function createSystemInfo(browserConfig) {
return Object.freeze({
...systemInfoBase,
// some information only available on main thread, we should read and pass to worker
pixelRatio,
pixelWidth: screenWidth,
pixelHeight: screenHeight,
...browserConfig,
});
}
export class LynxViewInstance {
parentDom;
initData;
globalprops;
templateUrl;
rootDom;
mtsRealm;
isSSR;
transformVW;
transformVH;
transformREM;
mainThreadGlobalThis;
mtsWasmBinding;
backgroundThread;
i18nManager;
exposureServices;
webElementsLoadingPromises = [];
#queryComponentCache = new Map();
#pageConfig;
#nativeModulesMap;
#napiModulesMap;
lepusCodeUrls = new Map();
systemInfo;
constructor(parentDom, initData, globalprops, templateUrl, rootDom, mtsRealm, isSSR, lynxGroupId, nativeModulesMap = {}, napiModulesMap = {}, initI18nResources, transformVW = false, transformVH = false, transformREM = false, browserConfig) {
this.parentDom = parentDom;
this.initData = initData;
this.globalprops = globalprops;
this.templateUrl = templateUrl;
this.rootDom = rootDom;
this.mtsRealm = mtsRealm;
this.isSSR = isSSR;
this.transformVW = transformVW;
this.transformVH = transformVH;
this.transformREM = transformREM;
this.systemInfo = createSystemInfo(browserConfig);
if (!isSSR) {
this.rootDom.append(linkElement.cloneNode(false));
}
this.#nativeModulesMap = nativeModulesMap;
this.#napiModulesMap = napiModulesMap;
this.mainThreadGlobalThis = mtsRealm.globalWindow;
this.backgroundThread = new BackgroundThread(lynxGroupId, this);
this.i18nManager = new I18nManager(this.backgroundThread, this.rootDom, initI18nResources);
this.mtsWasmBinding = new WASMJSBinding(this);
this.exposureServices = new ExposureServices(this);
this.backgroundThread.markTiming('create_lynx_start');
}
onPageConfigReady(config) {
if (this.#pageConfig) {
return;
}
// create element APIs
this.#pageConfig = config;
const enableCSSSelector = config['enableCSSSelector'] == 'true';
const defaultDisplayLinear = config['defaultDisplayLinear'] == 'true';
const defaultOverflowVisible = config['defaultOverflowVisible'] == 'true';
Object.assign(this.mtsRealm.globalWindow, createElementAPI(this.rootDom, this.mtsWasmBinding, enableCSSSelector, defaultDisplayLinear, defaultOverflowVisible, this.transformVW, this.transformVH, this.transformREM), createMainThreadGlobalAPIs(this));
}
onStyleInfoReady(currentUrl) {
if (this.mtsWasmBinding.wasmContext) {
const resource = templateManager.getStyleSheet(currentUrl);
if (resource) {
this.mtsWasmBinding.wasmContext.push_style_sheet(resource, this.templateUrl === currentUrl ? undefined : currentUrl);
}
}
}
async onMTSScriptsLoaded(currentUrl, isLazy) {
this.backgroundThread.markTiming('lepus_execute_start');
const urlMap = templateManager.getBundle(currentUrl)
?.lepusCode;
this.lepusCodeUrls.set(currentUrl, urlMap);
if (!isLazy) {
await this.mtsRealm.loadScript(urlMap['root']);
this.onMTSScriptsExecuted();
}
}
onMTSScriptsExecuted() {
this.backgroundThread.markTiming('lepus_execute_end');
this.webElementsLoadingPromises.length = 0;
this.backgroundThread.markTiming('data_processor_start');
const processedData = this.#pageConfig?.['enableJSDataProcessor'] !== 'true'
&& this.mainThreadGlobalThis.processData
? this.mainThreadGlobalThis.processData?.(this.initData)
: this.initData;
this.backgroundThread.markTiming('data_processor_end');
this.backgroundThread.startWebWorker(processedData, this.globalprops, templateManager.getBundle(this.templateUrl).config.cardType, templateManager.getBundle(this.templateUrl)?.customSections, this.#nativeModulesMap, this.#napiModulesMap);
if (this.isSSR) {
this.rootDom.querySelector('[part="page"]')?.remove();
}
this.mainThreadGlobalThis.renderPage?.(processedData);
this.mainThreadGlobalThis.__FlushElementTree();
}
async onBTSScriptsLoaded(url) {
const btsUrls = templateManager.getBundle(url)
?.backgroundCode;
await this.backgroundThread.updateBTSChunk(url, btsUrls);
this.backgroundThread.startBTS();
}
loadUnknownElement(tagName) {
if (tagName.includes('-') && !customElements.get(tagName)) {
this.rootDom.dispatchEvent(new CustomEvent(loadUnknownElementEventName, {
detail: {
tagName,
},
}));
this.webElementsLoadingPromises.push(customElements.whenDefined(tagName).then(() => { }));
}
}
queryComponent(url) {
if (this.#queryComponentCache.has(url)) {
return this.#queryComponentCache.get(url);
}
const promise = templateManager.fetchBundle(url, Promise.resolve(this), this.transformVW, this.transformVH, this.transformREM, {
enableCSSSelector: this.#pageConfig['enableCSSSelector'],
})
.then(async () => {
const urlMap = this.lepusCodeUrls.get(url);
const rootUrl = urlMap?.['root'];
if (!rootUrl) {
throw new Error(`[lynx-web] Missing root URL for component: ${url}`);
}
let lepusRootChunkExport = await this.mtsRealm.loadScript(rootUrl);
lepusRootChunkExport = this.mainThreadGlobalThis.processEvalResult?.(lepusRootChunkExport, url) ?? lepusRootChunkExport;
return lepusRootChunkExport;
});
this.#queryComponentCache.set(url, promise);
return promise;
}
async updateData(data, processorName) {
const processedData = this.#pageConfig['enableJSDataProcessor'] !== 'true'
&& this.mainThreadGlobalThis.processData
? this.mainThreadGlobalThis.processData(data, processorName)
: data;
this.mainThreadGlobalThis.updatePage?.(processedData, { processorName });
await this.backgroundThread.updateData(processedData, { processorName });
}
async updateGlobalProps(data) {
await this.backgroundThread.updateGlobalProps(data);
}
reportError(error, release, fileName) {
this.rootDom.dispatchEvent(new CustomEvent('error', {
detail: {
sourceMap: {
offset: {
line: 2,
col: 0,
},
},
error,
release,
fileName,
},
bubbles: true,
cancelable: true,
composed: true,
}));
}
async [Symbol.asyncDispose]() {
await this.backgroundThread[Symbol.asyncDispose]();
this.exposureServices.dispose();
requestIdleCallbackImpl(() => {
this.mtsWasmBinding.dispose();
});
}
}
//# sourceMappingURL=LynxViewInstance.js.map