UNPKG

@lynx-js/offscreen-document

Version:

Offscreen Document allows developers to use particular DOM in WebWorker

190 lines 7.5 kB
// 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 } from '../types/ElementOperation.js'; function emptyHandler() { // no-op } const otherPropertyNames = [ 'detail', 'keyCode', 'charCode', 'elapsedTime', 'propertyName', 'pseudoElement', 'animationName', 'touches', 'targetTouches', 'changedTouches', ]; const blockList = new Set([ 'isTrusted', 'target', 'currentTarget', 'type', 'bubbles', 'window', 'self', 'view', 'srcElement', 'eventPhase', ]); function transferToCloneable(value) { if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null || value === undefined) { return value; } else if (value[Symbol.iterator]) { return [...value].map(transferToCloneable); } else if (typeof value === 'object' && !(value instanceof EventTarget)) { const obj = {}; for (const key in value) { if (!blockList.has(key)) { obj[key] = transferToCloneable(value[key]); } } return obj; } } export function initOffscreenDocument(options) { const { shadowRoot, onEvent } = options; const enabledEvents = new Set(); const uniqueIdToElement = [new WeakRef(shadowRoot)]; const elementToUniqueId = new WeakMap(); function _getElement(uniqueId) { const element = uniqueIdToElement[uniqueId]?.deref(); if (element) { return element; } else { throw new Error(`[lynx-web] cannot find element with uniqueId: ${uniqueId}`); } } function _eventHandler(ev) { if (ev.eventPhase !== Event.CAPTURING_PHASE && ev.currentTarget !== shadowRoot) { return; } const target = ev.target; if (target && elementToUniqueId.has(target)) { const targetUniqueId = elementToUniqueId.get(target); const eventType = ev.type; const otherProperties = {}; for (const propertyName of otherPropertyNames) { if (propertyName in ev) { // @ts-expect-error otherProperties[propertyName] = transferToCloneable(ev[propertyName]); } } onEvent(eventType, targetUniqueId, ev.bubbles, otherProperties); } } function decodeOperation(operations) { if (operations.length === 0) { return; } let offset = 0; const { CreateElement, SetAttribute, RemoveAttribute, Append, Remove, ReplaceWith, InsertBefore, EnableEvent, RemoveChild, StyleDeclarationSetProperty, StyleDeclarationRemoveProperty, SetTextContent, sheetInsertRule, sheetRuleUpdateCssText, } = OperationType; let op; while ((op = operations[offset++])) { const uid = operations[offset++]; if (op === CreateElement) { const element = document.createElement(operations[offset++]); uniqueIdToElement[uid] = new WeakRef(element); elementToUniqueId.set(element, uid); } else { const target = _getElement(uid); switch (op) { case SetAttribute: { const key = operations[offset++]; const value = operations[offset++]; target.setAttribute(key, value); } break; case RemoveAttribute: { target.removeAttribute(operations[offset++]); } break; case Append: { const childrenLength = operations[offset++]; for (let i = 0; i < childrenLength; i++) { const id = operations[offset++]; const child = _getElement(id); target.appendChild(child); } } break; case Remove: target.remove(); break; case ReplaceWith: { const childrenLength = operations[offset++]; const newChildren = operations.slice(offset, offset + childrenLength).map((id) => _getElement(id)); offset += childrenLength; target.replaceWith(...newChildren); } break; case InsertBefore: { const kid = _getElement(operations[offset++]); const refUid = operations[offset++]; const ref = refUid ? _getElement(refUid) : null; target.insertBefore(kid, ref); } break; case EnableEvent: const eventType = operations[offset++]; target.addEventListener(eventType, emptyHandler, { passive: true }); if (!enabledEvents.has(eventType)) { shadowRoot.addEventListener(eventType, _eventHandler, { passive: true, capture: true }); enabledEvents.add(eventType); } break; case RemoveChild: { const kid = _getElement(operations[offset++]); target.removeChild(kid); } break; case StyleDeclarationSetProperty: { target.style.setProperty(operations[offset++], operations[offset++], operations[offset++]); } break; case StyleDeclarationRemoveProperty: { target.style.removeProperty(operations[offset++]); } break; case SetTextContent: target.textContent = operations[offset++]; break; case sheetInsertRule: { const index = operations[offset++]; const rule = operations[offset++]; target.sheet.insertRule(rule, index); } break; case sheetRuleUpdateCssText: { const index = operations[offset++]; target.sheet .cssRules[index].style.cssText = operations[offset++]; } break; } } } } return { decodeOperation, }; } //# sourceMappingURL=initOffscreenDocument.js.map