// Copyright 2024 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. /** * Implements the patch application logic for the snapshot system. * This module is responsible for interpreting and executing patch operations * that were generated in the background thread, applying them to the DOM * in the main thread. * * The module handles various operations like element creation, insertion, * removal, and attribute updates, ensuring they are applied in the correct * order and with proper error handling. */ import { sendCtxNotFoundEventToBackground } from './error.js'; import { applyNodesRefInsertBefore, applyNodesRefRemoveChild } from './nodesRefApply.js'; import type { SnapshotPatch } from './snapshotPatch.js'; import { SnapshotOperation } from './snapshotPatch.js'; import { SnapshotInstance, snapshotCreatorMap, snapshotInstanceManager } from '../../snapshot/snapshot.js'; /** * Applies a patch of snapshot operations to the main thread. * This is the counterpart to the patch generation in the background thread. * Each operation in the patch is processed sequentially to update the DOM. */ export function snapshotPatchApply(snapshotPatch: SnapshotPatch, commitId: number, isHydration: boolean): void { const length = snapshotPatch.length; for (let i = 0; i < length; ++i) { switch (snapshotPatch[i]) { case SnapshotOperation.CreateElement: { const type = snapshotPatch[++i] as string; const id = snapshotPatch[++i] as number; new SnapshotInstance(type, id); break; } case SnapshotOperation.InsertBefore: { const parentId = snapshotPatch[++i] as number; const childId = snapshotPatch[++i] as number; const beforeId = snapshotPatch[++i] as number | undefined; const __slotIndex = snapshotPatch[++i] as number; const parent = snapshotInstanceManager.values.get(parentId); const child = snapshotInstanceManager.values.get(childId); const existingNode = snapshotInstanceManager.values.get(beforeId!); if (!parent || !child) { sendCtxNotFoundEventToBackground(parent ? childId : parentId, { operation: 'InsertBefore', commitId, isHydration, parent: parentId, child: childId, missingNode: parent ? 2 : (child ? 1 : 3), }); } else { child.__slotIndex = __slotIndex; parent.insertBefore(child, existingNode); } break; } case SnapshotOperation.RemoveChild: { const parentId = snapshotPatch[++i] as number; const childId = snapshotPatch[++i] as number; const parent = snapshotInstanceManager.values.get(parentId); const child = snapshotInstanceManager.values.get(childId); if (!parent || !child) { sendCtxNotFoundEventToBackground(parent ? childId : parentId, { operation: 'RemoveChild', commitId, isHydration, parent: parentId, child: childId, missingNode: parent ? 2 : (child ? 1 : 3), }); } else { parent.removeChild(child); } break; } case SnapshotOperation.nodesRefInsertBefore: { const identifier = snapshotPatch[++i] as string; const childId = snapshotPatch[++i] as number; const beforeId = snapshotPatch[++i] as number | undefined; const child = snapshotInstanceManager.values.get(childId); if (child) { applyNodesRefInsertBefore(identifier, child, beforeId); } else { sendCtxNotFoundEventToBackground(childId, { operation: 'nodesRefInsertBefore', commitId, isHydration, }); } break; } case SnapshotOperation.nodesRefRemoveChild: { const identifier = snapshotPatch[++i] as string; const childId = snapshotPatch[++i] as number; const child = snapshotInstanceManager.values.get(childId); if (child) { applyNodesRefRemoveChild(identifier, child); } else { sendCtxNotFoundEventToBackground(childId, { operation: 'nodesRefRemoveChild', commitId, isHydration, }); } break; } case SnapshotOperation.SetAttribute: { const id = snapshotPatch[++i] as number; const dynamicPartIndex = snapshotPatch[++i] as number; const value = snapshotPatch[++i]; const si = snapshotInstanceManager.values.get(id); if (si) { si.setAttribute(dynamicPartIndex, value); } else { sendCtxNotFoundEventToBackground(id, { operation: 'SetAttribute', commitId, isHydration }); } break; } case SnapshotOperation.SetAttributes: { const id = snapshotPatch[++i] as number; const values = snapshotPatch[++i]; const si = snapshotInstanceManager.values.get(id); if (si) { si.setAttribute('values', values); } else { sendCtxNotFoundEventToBackground(id, { operation: 'SetAttributes', commitId, isHydration }); } break; } case SnapshotOperation.DEV_ONLY_AddSnapshot: { if (__DEV__) { const uniqID = snapshotPatch[++i] as string; const snapshotCreator = snapshotPatch[++i] as string; // HMR-related // Update the evaluated snapshots from JS. snapshotCreatorMap[uniqID] = evaluate<(uniqId: string) => string>(snapshotCreator); } break; } case SnapshotOperation.DEV_ONLY_SetSnapshotEntryName: { if (__DEV__) { const uniqID = snapshotPatch[++i] as string; const entryName = snapshotPatch[++i] as string; // HMR-related // Update the evaluated snapshot entryName from JS. const source = snapshotCreatorMap[uniqID]?.toString(); if (source?.includes('globDynamicComponentEntry')) { try { snapshotCreatorMap[uniqID] = evaluate<(uniqId: string) => string>( source.replace(/globDynamicComponentEntry/g, JSON.stringify(entryName)), ); } catch (e) { console.warn(`[ReactLynx] failed to rewrite snapshot entryName for ${uniqID}`, e); } } } break; } // case SnapshotOperation.DEV_ONLY_RegisterWorklet: { // // HMR-related // if (__DEV__) { // const hash: string = snapshotPatch[++i]; // const fnStr: string = snapshotPatch[++i]; // const fn = evaluate<(ctx: SnapshotInstance) => FiberElement[]>(fnStr); // registerWorklet('main-thread', hash, fn); // } // break; // } } } } /* v8 ignore start */ /** * Deserializes a stringified snapshot creator for HMR. */ function evaluate(code: string): T { if (__DEV__) { // Creators receive the runtime as a call argument (see `SnapshotCreator`). // Direct `eval` is kept for the legacy `__runtime__ || require(...)` // fallback, which resolves `__webpack_require__` from this scope on // runtimes that call creators with a single argument. return eval(`(() => ${code})()`) as T; } throw new Error('unreachable: evaluate is not supported in production'); } /* v8 ignore stop */