From 97137fb843c0d108ff1214262301dc5d57e79a35 Mon Sep 17 00:00:00 2001 From: pupiltong <12288479+PupilTong@users.noreply.github.com> Date: Mon, 9 Jun 2025 22:15:51 +0800 Subject: [PATCH 1/2] refactor: make the opcode be a plain array --- .changeset/nice-spoons-relax.md | 9 + .../src/main/initOffscreenDocument.ts | 145 +++++++---- .../src/types/ElementOperation.ts | 231 +++++++++--------- .../webworker/OffscreenCSSStyleDeclaration.ts | 20 +- .../src/webworker/OffscreenDocument.ts | 27 +- .../src/webworker/OffscreenElement.ts | 107 ++++---- .../web-constants/src/endpoints.ts | 3 +- 7 files changed, 302 insertions(+), 240 deletions(-) create mode 100644 .changeset/nice-spoons-relax.md diff --git a/.changeset/nice-spoons-relax.md b/.changeset/nice-spoons-relax.md new file mode 100644 index 0000000000..5cfdff0514 --- /dev/null +++ b/.changeset/nice-spoons-relax.md @@ -0,0 +1,9 @@ +--- +"@lynx-js/offscreen-document": patch +"@lynx-js/web-constants": patch +"@lynx-js/web-core": patch +--- + +refactor: make the opcode be a plain array + +#1042 diff --git a/packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts b/packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts index 67d4b75edd..91ad007e48 100644 --- a/packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts +++ b/packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts @@ -2,10 +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 { - OperationType, - type ElementOperation, -} from '../types/ElementOperation.js'; +import { OperationType } from '../types/ElementOperation.js'; function emptyHandler() { // no-op @@ -105,84 +102,138 @@ export function initOffscreenDocument(options: { } } - function decodeOperation(operations: ElementOperation[]) { - for (const op of operations) { - if (op.type === OperationType.CreateElement) { - const element = document.createElement(op.tag); - uniqueIdToElement[op.uid] = new WeakRef(element); - elementToUniqueId.set(element, op.uid); + function decodeOperation(operations: (string | number)[]) { + if (operations.length === 0) { + return; + } + let offset: number = 0; + const { + CreateElement, + SetAttribute, + RemoveAttribute, + Append, + Remove, + ReplaceWith, + InsertBefore, + EnableEvent, + RemoveChild, + StyleDeclarationSetProperty, + StyleDeclarationRemoveProperty, + SetInnerHTML, + sheetInsertRule, + sheetRuleUpdateCssText, + End, + } = OperationType; + let op: number | string | undefined; + while ((op = operations[offset++]) && op !== End) { + const uid = operations[offset++] as number; + if (op === CreateElement) { + const element = document.createElement(operations[offset++] as string); + uniqueIdToElement[uid] = new WeakRef(element); + elementToUniqueId.set(element, uid); } else { - const target = _getElement(op.uid); - switch (op.type) { - case OperationType.SetAttribute: - target.setAttribute(op.key, op.value); + const target = _getElement(uid); + switch (op) { + case SetAttribute: + { + const key = operations[offset++] as string; + const value = operations[offset++] as string; + target.setAttribute(key, value); + } break; - case OperationType.RemoveAttribute: - target.removeAttribute(op.key); + case RemoveAttribute: + { + target.removeAttribute(operations[offset++] as string); + } break; - case OperationType.Append: + case Append: { - const children = op.cid.map(id => _getElement(id)); - target.append(...children); + const childrenLength = operations[offset++] as number; + for (let i = 0; i < childrenLength; i++) { + const id = operations[offset++] as number; + const child = _getElement(id); + target.appendChild(child); + } } break; - case OperationType.Remove: + case Remove: target.remove(); break; - case OperationType.ReplaceWith: - const newChildren = op.nid.map(id => _getElement(id)); - target.replaceWith(...newChildren); + case ReplaceWith: + { + const childrenLength = operations[offset++] as number; + const newChildren: HTMLElement[] = operations.slice( + offset, + offset + childrenLength, + ).map((id) => _getElement(id as number)); + offset += childrenLength; + target.replaceWith(...newChildren); + } break; - case OperationType.InsertBefore: + case InsertBefore: { - const kid = _getElement(op.cid); - const ref = op.ref ? _getElement(op.ref) : null; + const kid = _getElement(operations[offset++] as number); + const refUid = operations[offset++] as number; + const ref = refUid ? _getElement(refUid) : null; target.insertBefore(kid, ref); } break; - case OperationType.EnableEvent: + case EnableEvent: + const eventType = operations[offset++] as string; target.addEventListener( - op.eventType, + eventType, emptyHandler, { passive: true }, ); - if (!enabledEvents.has(op.eventType)) { + if (!enabledEvents.has(eventType)) { shadowRoot.addEventListener( - op.eventType, + eventType, _eventHandler, { passive: true, capture: true }, ); - enabledEvents.add(op.eventType); + enabledEvents.add(eventType); } break; - case OperationType.RemoveChild: + case RemoveChild: { - const kid = _getElement(op.cid); + const kid = _getElement(operations[offset++] as number); target.removeChild(kid); } break; - case OperationType.StyleDeclarationSetProperty: + case StyleDeclarationSetProperty: { - target.style.setProperty(op.property, op.value, op.priority); + target.style.setProperty( + operations[offset++] as string, + operations[offset++] as string, + operations[offset++] as string, + ); } break; - case OperationType.StyleDeclarationRemoveProperty: + case StyleDeclarationRemoveProperty: { - target.style.removeProperty(op.property); + target.style.removeProperty(operations[offset++] as string); } break; - case OperationType.SetInnerHTML: - target.innerHTML = op.text; + case SetInnerHTML: + target.innerHTML = operations[offset++] as string; break; - case OperationType.sheetInsertRule: - (target as HTMLStyleElement).sheet!.insertRule( - op.rule, - op.index, - ); + case sheetInsertRule: + { + const index = operations[offset++] as number; + const rule = operations[offset++] as string; + (target as HTMLStyleElement).sheet!.insertRule( + rule, + index, + ); + } break; - case OperationType.sheetRuleUpdateCssText: - ((target as HTMLStyleElement).sheet! - .cssRules[op.index]! as CSSStyleRule).style.cssText = op.cssText; + case sheetRuleUpdateCssText: + { + const index = operations[offset++] as number; + ((target as HTMLStyleElement).sheet! + .cssRules[index]! as CSSStyleRule).style.cssText = + operations[offset++] as string; + } break; } } diff --git a/packages/web-platform/offscreen-document/src/types/ElementOperation.ts b/packages/web-platform/offscreen-document/src/types/ElementOperation.ts index ae517c075a..86dfcbd7eb 100644 --- a/packages/web-platform/offscreen-document/src/types/ElementOperation.ts +++ b/packages/web-platform/offscreen-document/src/types/ElementOperation.ts @@ -17,119 +17,122 @@ export const OperationType = { SetInnerHTML: 12, sheetInsertRule: 13, sheetRuleUpdateCssText: 14, + End: 0, } as const; -type IOperationType = typeof OperationType; -interface ElementOperationBase { - type: IOperationType[keyof IOperationType]; - /** - * uniqueId - */ - uid: number; -} - -export interface CreateOperation extends ElementOperationBase { - type: IOperationType['CreateElement']; - tag: string; -} - -export interface SetAttributeOperation extends ElementOperationBase { - type: IOperationType['SetAttribute']; - key: string; - value: string; -} -export interface RemoveAttributeOperation extends ElementOperationBase { - type: IOperationType['RemoveAttribute']; - key: string; -} - -export interface AppendOperation extends ElementOperationBase { - type: IOperationType['Append']; - /** - * child uniqueId - */ - cid: number[]; -} - -export interface RemoveOperation extends ElementOperationBase { - type: IOperationType['Remove']; -} - -export interface InsertBeforeOperation extends ElementOperationBase { - type: IOperationType['InsertBefore']; - /** - * child uniqueId - */ - cid: number; - ref?: number | undefined; -} - -export interface ReplaceOperation extends ElementOperationBase { - type: IOperationType['ReplaceWith']; - /** - * the new element's unique id. - */ - nid: number[]; -} - -export interface EnableEventOperation extends ElementOperationBase { - type: IOperationType['EnableEvent']; - eventType: string; -} - -export interface RemoveChildOperation extends ElementOperationBase { - type: IOperationType['RemoveChild']; - /** - * the child element's unique id to be removed. - */ - cid: number; -} - -export interface StyleDeclarationSetPropertyOperation - extends ElementOperationBase -{ - type: IOperationType['StyleDeclarationSetProperty']; - property: string; - value: string; - priority: string | undefined | ''; -} - -export interface StyleDeclarationRemovePropertyOperation - extends ElementOperationBase -{ - type: IOperationType['StyleDeclarationRemoveProperty']; - property: string; -} - -export interface SetInnerHTMLOperation extends ElementOperationBase { - type: IOperationType['SetInnerHTML']; - text: string; -} - -export interface SheetInsertRuleOperation extends ElementOperationBase { - type: IOperationType['sheetInsertRule']; - rule: string; - index: number; -} - -export interface SheetRuleUpdateCssTextOperation extends ElementOperationBase { - type: IOperationType['sheetRuleUpdateCssText']; - index: number; - cssText: string; -} - -export type ElementOperation = - | EnableEventOperation - | ReplaceOperation - | InsertBeforeOperation - | CreateOperation - | SetAttributeOperation - | RemoveAttributeOperation - | AppendOperation - | RemoveOperation - | RemoveChildOperation - | StyleDeclarationSetPropertyOperation - | StyleDeclarationRemovePropertyOperation - | SetInnerHTMLOperation - | SheetInsertRuleOperation - | SheetRuleUpdateCssTextOperation; +// keep these types for opcode hint + +// type IOperationType = typeof OperationType; +// interface ElementOperationBase { +// type: IOperationType[keyof IOperationType]; +// /** +// * uniqueId +// */ +// uid: number; +// } + +// export interface CreateOperation extends ElementOperationBase { +// type: IOperationType['CreateElement']; +// tag: string; +// } + +// export interface SetAttributeOperation extends ElementOperationBase { +// type: IOperationType['SetAttribute']; +// key: string; +// value: string; +// } +// export interface RemoveAttributeOperation extends ElementOperationBase { +// type: IOperationType['RemoveAttribute']; +// key: string; +// } + +// export interface AppendOperation extends ElementOperationBase { +// type: IOperationType['Append']; +// /** +// * child uniqueId +// */ +// cid: number[]; +// } + +// export interface RemoveOperation extends ElementOperationBase { +// type: IOperationType['Remove']; +// } + +// export interface InsertBeforeOperation extends ElementOperationBase { +// type: IOperationType['InsertBefore']; +// /** +// * child uniqueId +// */ +// cid: number; +// ref?: number | undefined; +// } + +// export interface ReplaceOperation extends ElementOperationBase { +// type: IOperationType['ReplaceWith']; +// /** +// * the new element's unique id. +// */ +// nid: number[]; +// } + +// export interface EnableEventOperation extends ElementOperationBase { +// type: IOperationType['EnableEvent']; +// eventType: string; +// } + +// export interface RemoveChildOperation extends ElementOperationBase { +// type: IOperationType['RemoveChild']; +// /** +// * the child element's unique id to be removed. +// */ +// cid: number; +// } + +// export interface StyleDeclarationSetPropertyOperation +// extends ElementOperationBase +// { +// type: IOperationType['StyleDeclarationSetProperty']; +// property: string; +// value: string; +// priority: string | undefined | ''; +// } + +// export interface StyleDeclarationRemovePropertyOperation +// extends ElementOperationBase +// { +// type: IOperationType['StyleDeclarationRemoveProperty']; +// property: string; +// } + +// export interface SetInnerHTMLOperation extends ElementOperationBase { +// type: IOperationType['SetInnerHTML']; +// text: string; +// } + +// export interface SheetInsertRuleOperation extends ElementOperationBase { +// type: IOperationType['sheetInsertRule']; +// rule: string; +// index: number; +// } + +// export interface SheetRuleUpdateCssTextOperation extends ElementOperationBase { +// type: IOperationType['sheetRuleUpdateCssText']; +// index: number; +// cssText: string; +// } + +// export type ElementOperation = +// | EnableEventOperation +// | ReplaceOperation +// | InsertBeforeOperation +// | CreateOperation +// | SetAttributeOperation +// | RemoveAttributeOperation +// | AppendOperation +// | RemoveOperation +// | RemoveChildOperation +// | StyleDeclarationSetPropertyOperation +// | StyleDeclarationRemovePropertyOperation +// | SetInnerHTMLOperation +// | SheetInsertRuleOperation +// | SheetRuleUpdateCssTextOperation; diff --git a/packages/web-platform/offscreen-document/src/webworker/OffscreenCSSStyleDeclaration.ts b/packages/web-platform/offscreen-document/src/webworker/OffscreenCSSStyleDeclaration.ts index 853d144cab..071c280222 100644 --- a/packages/web-platform/offscreen-document/src/webworker/OffscreenCSSStyleDeclaration.ts +++ b/packages/web-platform/offscreen-document/src/webworker/OffscreenCSSStyleDeclaration.ts @@ -25,13 +25,13 @@ export class OffscreenCSSStyleDeclaration { value: string, priority?: 'important' | undefined | '', ): void { - this._parent[ancestorDocument][operations].push({ - type: OperationType['StyleDeclarationSetProperty'], - uid: this._parent[uniqueId], + this._parent[ancestorDocument][operations].push( + OperationType['StyleDeclarationSetProperty'], + this._parent[uniqueId], property, - value: value, - priority: priority, - }); + value, + priority ?? '', + ); const currentStyle = this._parent.getAttribute('style') ?? ''; this._parent[_attributes].set( 'style', @@ -40,11 +40,11 @@ export class OffscreenCSSStyleDeclaration { } removeProperty(property: string): void { - this._parent[ancestorDocument][operations].push({ - type: OperationType['StyleDeclarationRemoveProperty'], - uid: this._parent[uniqueId], + this._parent[ancestorDocument][operations].push( + OperationType['StyleDeclarationRemoveProperty'], + this._parent[uniqueId], property, - }); + ); const currentStyle = this._parent.getAttribute('style') ?? ''; this._parent[_attributes].set( 'style', diff --git a/packages/web-platform/offscreen-document/src/webworker/OffscreenDocument.ts b/packages/web-platform/offscreen-document/src/webworker/OffscreenDocument.ts index 02b58d2973..6d939ccc9d 100644 --- a/packages/web-platform/offscreen-document/src/webworker/OffscreenDocument.ts +++ b/packages/web-platform/offscreen-document/src/webworker/OffscreenDocument.ts @@ -1,10 +1,7 @@ // 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 { - OperationType, - type ElementOperation, -} from '../types/ElementOperation.js'; +import { OperationType } from '../types/ElementOperation.js'; import { _attributes, _children, @@ -37,7 +34,7 @@ export class OffscreenDocument extends OffscreenElement { /** * @private */ - [operations]: ElementOperation[] = []; + [operations]: (string | number)[] = []; /** * @private @@ -51,18 +48,18 @@ export class OffscreenDocument extends OffscreenElement { [enableEvent]: (eventType: string, uid: number) => void; constructor( private _callbacks: { - onCommit: (operations: ElementOperation[]) => void; + onCommit: (operations: (string | number)[]) => void; }, ) { const enableEventImpl: (nm: string, uid: number) => void = ( eventType, uid, ) => { - this[operations].push({ - type: OperationType.EnableEvent, - eventType, + this[operations].push( + OperationType.EnableEvent, uid, - }); + eventType, + ); }; super('', 0); this[ancestorDocument] = this; @@ -80,11 +77,11 @@ export class OffscreenDocument extends OffscreenElement { const element = new OffscreenElement(tagName, uniqueId); element[ancestorDocument] = this; this[_uniqueIdToElement][uniqueId] = new WeakRef(element); - this[operations].push({ - type: OperationType.CreateElement, - uid: uniqueId, - tag: tagName, - }); + this[operations].push( + OperationType.CreateElement, + uniqueId, + tagName, + ); return element; } diff --git a/packages/web-platform/offscreen-document/src/webworker/OffscreenElement.ts b/packages/web-platform/offscreen-document/src/webworker/OffscreenElement.ts index e2205c5f36..0c6fb36c1c 100644 --- a/packages/web-platform/offscreen-document/src/webworker/OffscreenElement.ts +++ b/packages/web-platform/offscreen-document/src/webworker/OffscreenElement.ts @@ -59,21 +59,21 @@ export class OffscreenElement extends EventTarget { cssRules.splice(index, 0, { style: { set cssText(text: string) { - ancestor[operations].push({ - type: OperationType.sheetRuleUpdateCssText, + ancestor[operations].push( + OperationType.sheetRuleUpdateCssText, uid, - cssText: text, index, - }); + text, + ); }, }, }); - this[ancestorDocument][operations].push({ - type: OperationType.sheetInsertRule, + this[ancestorDocument][operations].push( + OperationType.sheetInsertRule, uid, - rule, index, - }); + rule, + ); return index; }, }; @@ -135,12 +135,12 @@ export class OffscreenElement extends EventTarget { setAttribute(qualifiedName: string, value: string): void { this[_attributes].set(qualifiedName, value); - this[ancestorDocument][operations].push({ - type: OperationType.SetAttribute, - uid: this[uniqueId], - key: qualifiedName, + this[ancestorDocument][operations].push( + OperationType.SetAttribute, + this[uniqueId], + qualifiedName, value, - }); + ); } getAttribute(qualifiedName: string): string | null { @@ -149,19 +149,20 @@ export class OffscreenElement extends EventTarget { removeAttribute(qualifiedName: string): void { this[_attributes].delete(qualifiedName); - this[ancestorDocument][operations].push({ - type: OperationType.RemoveAttribute, - uid: this[uniqueId], - key: qualifiedName, - }); + this[ancestorDocument][operations].push( + OperationType.RemoveAttribute, + this[uniqueId], + qualifiedName, + ); } append(...nodes: (OffscreenElement)[]): void { - this[ancestorDocument][operations].push({ - type: OperationType.Append, - uid: this[uniqueId], - cid: nodes.map(node => node[uniqueId]), - }); + this[ancestorDocument][operations].push( + OperationType.Append, + this[uniqueId], + nodes.length, + ...nodes.map(node => node[uniqueId]), + ); for (const node of nodes) { node._remove(); node._parentElement = this; @@ -170,11 +171,12 @@ export class OffscreenElement extends EventTarget { } appendChild(node: OffscreenElement): OffscreenElement { - this[ancestorDocument][operations].push({ - type: OperationType.Append, - uid: this[uniqueId], - cid: [node[uniqueId]], - }); + this[ancestorDocument][operations].push( + OperationType.Append, + this[uniqueId], + 1, + node[uniqueId], + ); node._remove(); node._parentElement = this; this[_children].push(node); @@ -182,11 +184,12 @@ export class OffscreenElement extends EventTarget { } replaceWith(...nodes: (OffscreenElement)[]): void { - this[ancestorDocument][operations].push({ - type: OperationType.ReplaceWith, - uid: this[uniqueId], - nid: nodes.map(node => node[uniqueId]), - }); + this[ancestorDocument][operations].push( + OperationType.ReplaceWith, + this[uniqueId], + nodes.length, + ...nodes.map(node => node[uniqueId]), + ); if (this._parentElement) { const parent = this._parentElement; this._parentElement = null; @@ -203,10 +206,10 @@ export class OffscreenElement extends EventTarget { } remove(): void { - this[ancestorDocument][operations].push({ - type: OperationType.Remove, - uid: this[uniqueId], - }); + this[ancestorDocument][operations].push( + OperationType.Remove, + this[uniqueId], + ); this._remove(); } @@ -226,12 +229,12 @@ export class OffscreenElement extends EventTarget { this[_children].push(newNode); } - this[ancestorDocument][operations].push({ - type: OperationType.InsertBefore, - uid: this[uniqueId], - cid: newNode[uniqueId], - ref: refNode?.[uniqueId], - }); + this[ancestorDocument][operations].push( + OperationType.InsertBefore, + this[uniqueId], + newNode[uniqueId], + refNode?.[uniqueId] ?? 0, + ); return newNode; } @@ -248,11 +251,11 @@ export class OffscreenElement extends EventTarget { 'NotFoundError', ); } - this[ancestorDocument][operations].push({ - type: OperationType.RemoveChild, - uid: this[uniqueId], - cid: child![uniqueId], - }); + this[ancestorDocument][operations].push( + OperationType.RemoveChild, + this[uniqueId], + child![uniqueId], + ); child._remove(); return child; } @@ -267,11 +270,11 @@ export class OffscreenElement extends EventTarget { } set innerHTML(text: string) { - this[ancestorDocument][operations].push({ - type: OperationType.SetInnerHTML, + this[ancestorDocument][operations].push( + OperationType.SetInnerHTML, + this[uniqueId], text, - uid: this[uniqueId], - }); + ); for (const child of this.children) { (child as OffscreenElement).remove(); } diff --git a/packages/web-platform/web-constants/src/endpoints.ts b/packages/web-platform/web-constants/src/endpoints.ts index 2acfc30a7f..60915ca3d7 100644 --- a/packages/web-platform/web-constants/src/endpoints.ts +++ b/packages/web-platform/web-constants/src/endpoints.ts @@ -13,7 +13,6 @@ import type { IdentifierType, InvokeCallbackRes } from './types/NativeApp.js'; import type { LynxTemplate } from './types/LynxModule.js'; import type { NapiModulesMap } from './types/NapiModules.js'; import type { NativeModulesMap } from './types/NativeModules.js'; -import type { ElementOperation } from '@lynx-js/offscreen-document'; import type { BrowserConfig } from './types/PageConfig.js'; import type { ElementAnimationOptions } from './types/Element.js'; @@ -98,7 +97,7 @@ export const reportErrorEndpoint = createRpcEndpoint< export const flushElementTreeEndpoint = createRpcEndpoint< [ - operations: ElementOperation[], + operations: (string | number)[], ], void >('flushElementTree', false, true); From a46590de8b28af31ef67aafe1aaefba98db46642 Mon Sep 17 00:00:00 2001 From: pupiltong <12288479+PupilTong@users.noreply.github.com> Date: Tue, 10 Jun 2025 11:33:09 +0800 Subject: [PATCH 2/2] copilot --- .../offscreen-document/src/main/initOffscreenDocument.ts | 3 +-- .../offscreen-document/src/types/ElementOperation.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts b/packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts index 91ad007e48..06fe9becbc 100644 --- a/packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts +++ b/packages/web-platform/offscreen-document/src/main/initOffscreenDocument.ts @@ -122,10 +122,9 @@ export function initOffscreenDocument(options: { SetInnerHTML, sheetInsertRule, sheetRuleUpdateCssText, - End, } = OperationType; let op: number | string | undefined; - while ((op = operations[offset++]) && op !== End) { + while ((op = operations[offset++])) { const uid = operations[offset++] as number; if (op === CreateElement) { const element = document.createElement(operations[offset++] as string); diff --git a/packages/web-platform/offscreen-document/src/types/ElementOperation.ts b/packages/web-platform/offscreen-document/src/types/ElementOperation.ts index 86dfcbd7eb..bd2cf852be 100644 --- a/packages/web-platform/offscreen-document/src/types/ElementOperation.ts +++ b/packages/web-platform/offscreen-document/src/types/ElementOperation.ts @@ -17,7 +17,6 @@ export const OperationType = { SetInnerHTML: 12, sheetInsertRule: 13, sheetRuleUpdateCssText: 14, - End: 0, } as const; // keep these types for opcode hint