@lynx-js/offscreen-document
Version:
Offscreen Document allows developers to use particular DOM in WebWorker
190 lines • 7.5 kB
JavaScript
// 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, SetInnerHTML, 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 SetInnerHTML:
target.innerHTML = 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