translation-resilience 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@ import { installTranslationResilience } from 'translation-resilience';
8
8
  installTranslationResilience();
9
9
  ```
10
10
 
11
- Framework-agnostic (it patches the DOM layer, not React), dependency-free, ~2.3 kB min+gzip.
11
+ Framework-agnostic (it patches the DOM layer, not React), dependency-free, ~2.4 kB min+gzip.
12
12
 
13
13
  ## The problem
14
14
 
@@ -17,7 +17,7 @@ When Chrome translates a page, it rewrites the DOM underneath your framework:
17
17
  1. Adjacent `Text` nodes are merged (`normalize()`) — so `Lights: {count}` collapses into one run.
18
18
  2. The merged run is split into segments (numbers isolated) and each segment is wrapped in nested `<font style="vertical-align: inherit;">` elements containing **new** text nodes.
19
19
  3. The original text nodes are removed from the document — but React still owns and references them.
20
- 4. Translation can also delete text nodes outright and reorder inline elements to match target-language word order ([Chromium bug 872770](https://issues.chromium.org/issues/41410214)).
20
+ 4. Translation can also delete text nodes outright and reorder inline elements to match target-language word order ([Chromium bug 872770](https://issues.chromium.org/issues/41407169)).
21
21
 
22
22
  React keeps operating on the original, now-detached nodes:
23
23
 
@@ -82,9 +82,29 @@ installTranslationResilience({
82
82
  - **Reversible.** `installTranslationResilience()` returns an uninstall function that restores all prototypes and disconnects the observer. Calling install twice returns the same uninstall (idempotent).
83
83
  - **SSR-safe to import.** Native DOM entry points are captured lazily, so importing the module in Node is fine; only *calling* install requires a DOM.
84
84
 
85
- ### Cost
85
+ ## Performance cost
86
86
 
87
- One document-wide `MutationObserver` (childList + characterData, subtree). Record processing is linear in the number of mutations with small constants, and correlation state expires after 100 ms, so steady-state memory is effectively zero. The prototype patches add one `parentNode` comparison to `removeChild`/`insertBefore`/`appendChild` calls on the happy path.
87
+ Measured numbers, with an honest caveat up front: these are microbenchmarks from one machine (Apple Silicon, headless Chrome, production React 18 build; a 500-row × 4-column table, medians of 7 runs). The shim has **not yet been benchmarked inside a large production app** if you measure something different, please open an issue.
88
+
89
+ While translation is **not** active (the common case):
90
+
91
+ - **React-level overhead is small.** Re-rendering the table with three cells changing in every row, 50 commits: 27 ms → 41 ms total, ≈ 0.3 ms extra per full-table commit. Repeated mount+unmount of the whole table: +20%.
92
+ - **Per-operation costs are sub-microsecond.** Writes to an attached `text.data`: ~0.05 µs → ~0.3 µs each (setter indirection plus the observer allocating a characterData record). The worst case we could construct — a tight synchronous loop appending and removing individual detached text nodes, a pattern frameworks don't produce (they remove element subtrees as one operation) — costs ~1.8 µs per append+remove pair vs ~0.2 µs native.
93
+
94
+ Where the cost comes from: one document-wide `MutationObserver` (childList + characterData + oldValue, subtree) means the browser allocates a record per DOM mutation. The patched methods add a parent check per call; inserting a *detached* text node additionally drains and processes pending observer records. Correlation state expires after 100 ms and is purged incrementally (amortized O(1) per entry), so steady-state memory is effectively zero.
95
+
96
+ While translation **is** active, each update to translated text pays the restore → re-translate cycle: ≈ 14 µs per updated text run, so a commit updating 1,500 translated text runs at once costs ≈ 20 ms extra. The alternative without the shim is those updates never becoming visible at all. Translated pages that are idle cost nothing beyond the observer.
97
+
98
+ ### How React core could fix this (nearly for free)
99
+
100
+ Almost everything this package pays on the happy path is an *outsider tax*: the cost of reconstructing, from mutation records, knowledge the renderer already has. React knows which text nodes it owns (each HostText fiber holds its DOM node), what they should contain (`memoizedProps`), and what the DOM around them should look like (the fiber tree). A fix inside React would need none of this package's machinery:
101
+
102
+ - **Detection is one pointer comparison.** A text node displaced by translation has `parentNode === null`, so the silent-freeze case is caught by a single check inside `commitTextUpdate`, on an operation React is already performing — instead of a document-wide `MutationObserver` allocating a record (plus an `oldValue` string copy) for every DOM mutation on the page.
103
+ - **The crash half can cost literally nothing.** React 19 already wraps commit-phase deletions in try/catch — that is why it tears down to the nearest error boundary instead of white-screening. Upgrading that catch from "tear down" to "repair and continue" adds zero instructions to the non-throwing path.
104
+ - **Repair needs no archaeology.** This package tracks displacement groups through a correlation window because, from the outside, "which foreign nodes replaced mine, and what did mine say?" can only be answered by watching the mutations happen. React can instead rebuild the affected host element's children from fiber state — the moral equivalent of hydration-mismatch recovery — and let the translator re-translate the result. Same self-healing loop as this shim, none of the bookkeeping.
105
+ - **Nobody else pays.** The prototype patches here are page-global: every DOM user, React or not, pays the (small) toll. An in-core fix is scoped to React's own commit operations.
106
+
107
+ Against the numbers above: the measured shim overhead (~0.3 ms on a full-table commit, +20% on mount/unmount cycles) would drop to a branch-predicted pointer compare — effectively unmeasurable. A fix has been requested since 2017 ([facebook/react#11538](https://github.com/facebook/react/issues/11538)); the traditional objections — defensive checks don't belong in the commit hot path, and third-party DOM mutation is outside React's contract — are worth weighing against what userland has to do instead, which is this entire package. The same reasoning applies to any renderer that keeps references to the text nodes it creates (Vue, Ember, Svelte). We would be delighted for this package to be made obsolete.
88
108
 
89
109
  ## Testing your app against translation: the simulator
90
110
 
package/dist/index.cjs CHANGED
@@ -85,15 +85,23 @@ var recentCarrierAccumulated = /* @__PURE__ */ new Map();
85
85
  var pendingOrphans = [];
86
86
  function purgeExpired(now) {
87
87
  for (const [key, entry] of recentInsertedBefore) {
88
- if (now - entry.at > CORRELATION_WINDOW_MS) recentInsertedBefore.delete(key);
88
+ if (now - entry.at <= CORRELATION_WINDOW_MS) break;
89
+ recentInsertedBefore.delete(key);
89
90
  }
90
91
  for (const [key, entry] of recentOldValues) {
91
- if (now - entry.at > CORRELATION_WINDOW_MS) recentOldValues.delete(key);
92
+ if (now - entry.at <= CORRELATION_WINDOW_MS) break;
93
+ recentOldValues.delete(key);
92
94
  }
93
95
  for (const [key, entry] of recentCarrierAccumulated) {
94
- if (now - entry.at > CORRELATION_WINDOW_MS) recentCarrierAccumulated.delete(key);
96
+ if (now - entry.at <= CORRELATION_WINDOW_MS) break;
97
+ recentCarrierAccumulated.delete(key);
98
+ }
99
+ const firstFresh = pendingOrphans.findIndex((orphan) => now - orphan.at <= CORRELATION_WINDOW_MS);
100
+ if (firstFresh === -1) {
101
+ if (pendingOrphans.length > 0) pendingOrphans = [];
102
+ } else if (firstFresh > 0) {
103
+ pendingOrphans = pendingOrphans.slice(firstFresh);
95
104
  }
96
- pendingOrphans = pendingOrphans.filter((orphan) => now - orphan.at <= CORRELATION_WINDOW_MS);
97
105
  }
98
106
  function clearCorrelationState() {
99
107
  recentInsertedBefore.clear();
@@ -122,6 +130,7 @@ function mergeCarrierFor(removed, record, now) {
122
130
  if (carrierOriginalValue === void 0) return null;
123
131
  const accumulated = ((_c = (_b = recentCarrierAccumulated.get(carrier)) == null ? void 0 : _b.value) != null ? _c : "") + snapshotValue(removed);
124
132
  if (!((_d = carrier.nodeValue) != null ? _d : "").startsWith(carrierOriginalValue + accumulated)) return null;
133
+ recentCarrierAccumulated.delete(carrier);
125
134
  recentCarrierAccumulated.set(carrier, { at: now, value: accumulated });
126
135
  return carrier;
127
136
  }
@@ -215,6 +224,7 @@ function processRecords(records) {
215
224
  const entry = (_a = recentInsertedBefore.get(record.nextSibling)) != null ? _a : { at: now, nodes: [] };
216
225
  entry.at = now;
217
226
  entry.nodes.push(added);
227
+ recentInsertedBefore.delete(record.nextSibling);
218
228
  recentInsertedBefore.set(record.nextSibling, entry);
219
229
  }
220
230
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/resilience.ts"],"sourcesContent":["export type { TranslationResilienceOptions } from './resilience';\nexport { installTranslationResilience } from './resilience';\n","/**\n * Makes React (and any other text-node-owning renderer) resilient to browser\n * page translation (Chrome / Google Translate), which merges and replaces\n * Text nodes with `<font>` wrappers. React keeps references to the original,\n * now detached, Text nodes, so without this shim:\n *\n * - unmounting translated conditional text throws NotFoundError (removeChild)\n * - mounting content before translated text throws NotFoundError (insertBefore)\n * - updating translated text writes to the detached node — the visible page\n * silently never updates again (see facebook/react#11538)\n *\n * Strategy — \"re-adoption\", not error swallowing:\n *\n * 1. A document-wide MutationObserver watches mutations. Translation displaces\n * text nodes in a recognizable pattern: adjacent Text nodes are merged\n * (`normalize()`), then the merged run is replaced by wrapper elements\n * inserted next to it in the same task. We track a DISPLACEMENT GROUP per\n * replaced run: the ordered renderer-owned originals (with their\n * pre-translation values) and the run of replacement nodes standing in for\n * them. React's own commits never look like this: React processes\n * deletions before placements, so a node it removes is never adjacent to a\n * same-batch insertion at removal time, and React never merges text nodes.\n *\n * 2. Patched Node.prototype.removeChild / insertBefore / appendChild and the\n * nodeValue / data setters detect operations on displaced Text nodes and\n * first RESTORE the group — original nodes go back into the replacement\n * run's position, replacements are removed — then let the native operation\n * proceed. This repairs the renderer's ownership invariant: updates become\n * visible, removals remove the right content, and the translator\n * re-translates the freshly restored text via its own observer.\n *\n * 3. Translation can also delete text nodes outright and move inline elements\n * (word-order changes — Chromium bug 872770). Deletions within a\n * translation batch get an empty group with position hints so the node can\n * come back if React updates it. Once translation activity has been\n * detected, unrecoverable parent mismatches degrade to guarded best-effort\n * operations instead of throwing.\n *\n * Before any translation activity is detected, operations on untracked nodes\n * behave exactly as before — including throwing on genuine bugs.\n */\n\ninterface DisplacedOriginal {\n node: Text;\n /** The node's value before translation touched it (normalize mutates the merge target). */\n value: string;\n}\n\ninterface DisplacementGroup {\n parent: Node;\n /** Renderer-owned text nodes this group stands in for, in document order. */\n originals: DisplacedOriginal[];\n /** Nodes currently displaying the originals' content (empty if translation deleted the run). */\n replacement: Node[];\n /** Position hints captured at removal time, used when `replacement` is empty. */\n previousSiblingHint: Node | null;\n nextSiblingHint: Node | null;\n}\n\nconst displaced = new WeakMap<Text, DisplacementGroup>();\nconst groupByReplacementNode = new WeakMap<Node, DisplacementGroup>();\n/** Live merge targets (normalize) carrying content of already-detached originals. */\nconst pendingCarrierOriginals = new WeakMap<Text, DisplacedOriginal[]>();\n\nlet observer: MutationObserver | null = null;\nlet translationDetected = false;\nconst noopEvent = (_message: string): void => undefined;\nlet emitEvent: (message: string) => void = noopEvent;\n\ninterface DomNatives {\n insertBefore: typeof Node.prototype.insertBefore;\n removeChild: typeof Node.prototype.removeChild;\n appendChild: typeof Node.prototype.appendChild;\n nodeValueDescriptor: PropertyDescriptor;\n dataDescriptor: PropertyDescriptor;\n setNodeValue(node: Node, value: string | null): void;\n setData(node: CharacterData, value: string): void;\n}\n\nlet capturedNatives: DomNatives | null = null;\n\n/**\n * Native DOM entry points, captured once on first use rather than at module\n * load so that importing this module is safe in non-DOM environments (SSR);\n * only calling install requires a browser.\n */\nfunction domNatives(): DomNatives {\n if (capturedNatives) return capturedNatives;\n if (typeof Node === 'undefined' || typeof CharacterData === 'undefined') {\n throw new Error(\n 'translation-resilience requires a DOM. Call installTranslationResilience() from client-side code only.'\n );\n }\n const nodeValueDescriptor = Object.getOwnPropertyDescriptor(Node.prototype, 'nodeValue');\n const dataDescriptor = Object.getOwnPropertyDescriptor(CharacterData.prototype, 'data');\n const nodeValueSet = nodeValueDescriptor?.set;\n const dataSet = dataDescriptor?.set;\n if (!nodeValueDescriptor?.get || !nodeValueSet || !dataDescriptor?.get || !dataSet) {\n throw new Error('translation-resilience: expected accessor descriptors for nodeValue and data');\n }\n capturedNatives = {\n insertBefore: Node.prototype.insertBefore,\n removeChild: Node.prototype.removeChild,\n appendChild: Node.prototype.appendChild,\n nodeValueDescriptor,\n dataDescriptor,\n setNodeValue: (node, value) => nodeValueSet.call(node, value),\n setData: (node, value) => dataSet.call(node, value),\n };\n return capturedNatives;\n}\n\n/**\n * A fault in the shim itself must never make things worse than stock\n * behavior: every non-native code path runs through this guard, and on an\n * internal error the caller falls back to the native operation.\n */\nfunction guarded<T>(operation: string, run: () => T, fallback: T): T {\n try {\n return run();\n } catch (error) {\n emitEvent(`internal error in ${operation}: ${error instanceof Error ? error.message : String(error)}`);\n return fallback;\n }\n}\n\nfunction registerGroup(group: DisplacementGroup): void {\n if (!translationDetected) {\n translationDetected = true;\n emitEvent('translation activity detected');\n }\n for (const original of group.originals) displaced.set(original.node, group);\n for (const node of group.replacement) groupByReplacementNode.set(node, group);\n}\n\nfunction unregisterGroup(group: DisplacementGroup): void {\n for (const original of group.originals) displaced.delete(original.node);\n for (const node of group.replacement) groupByReplacementNode.delete(node);\n}\n\n/**\n * Correlation state must survive batch boundaries: the record stream can be\n * split at arbitrary points — the shim's own synchronous drains inside\n * patched DOM methods split a translator's mutation sequence across several\n * processRecords calls, and translators themselves spread work across tasks.\n * Entries are consumed on use and expire after a short wall-clock window so\n * that old insertions are never attributed to unrelated removals later.\n */\nconst CORRELATION_WINDOW_MS = 100;\n\ninterface TimedRun {\n at: number;\n nodes: Node[];\n}\ninterface TimedValue {\n at: number;\n value: string;\n}\ninterface PendingOrphan {\n at: number;\n parent: Node;\n removed: Text;\n previousSibling: Node | null;\n nextSibling: Node | null;\n}\n\n/** Nodes recently inserted, indexed by their at-insertion-time nextSibling. */\nconst recentInsertedBefore = new Map<Node, TimedRun>();\n/** First recently-seen characterData oldValue per node = value before the mutation sequence. */\nconst recentOldValues = new Map<Node, TimedValue>();\n/** Accumulated merged-away content per normalize target, for validation. */\nconst recentCarrierAccumulated = new Map<Text, TimedValue>();\n/** Text removals with no replacement — become deletion groups once translator activity is confirmed. */\nlet pendingOrphans: PendingOrphan[] = [];\n\nfunction purgeExpired(now: number): void {\n for (const [key, entry] of recentInsertedBefore) {\n if (now - entry.at > CORRELATION_WINDOW_MS) recentInsertedBefore.delete(key);\n }\n for (const [key, entry] of recentOldValues) {\n if (now - entry.at > CORRELATION_WINDOW_MS) recentOldValues.delete(key);\n }\n for (const [key, entry] of recentCarrierAccumulated) {\n if (now - entry.at > CORRELATION_WINDOW_MS) recentCarrierAccumulated.delete(key);\n }\n pendingOrphans = pendingOrphans.filter((orphan) => now - orphan.at <= CORRELATION_WINDOW_MS);\n}\n\nfunction clearCorrelationState(): void {\n recentInsertedBefore.clear();\n recentOldValues.clear();\n recentCarrierAccumulated.clear();\n pendingOrphans = [];\n}\n\n/** Replacement nodes a translator produces: <font> wrappers or plain text (revert). */\nfunction looksLikeTranslatorReplacement(node: Node): boolean {\n return node instanceof Text || node.nodeName === 'FONT';\n}\n\n/**\n * The run of nodes that took a removed node's place. A replaceChild-style\n * swap queues a single record carrying both sides — correlate those directly.\n * Translation instead inserts the wrapper(s) directly before the original and\n * then removes it as separate operations, so each wrapper's insertion record\n * has the original as its at-insertion-time nextSibling. Sibling pointers are\n * NOT walked at processing time — later mutations may already have\n * invalidated them.\n */\nfunction replacementRunFor(removed: Node, record: MutationRecord): Node[] {\n if (record.removedNodes.length === 1 && record.addedNodes.length === 1) {\n const added = record.addedNodes[0];\n return added ? [added] : [];\n }\n const entry = recentInsertedBefore.get(removed);\n if (!entry) return [];\n recentInsertedBefore.delete(removed);\n return entry.nodes.filter(looksLikeTranslatorReplacement);\n}\n\n/**\n * Detects a normalize() merge: the removed text's content was appended onto\n * the preceding Text sibling. The carrier must have a recent characterData\n * record (a renderer removing a node never mutates its neighbor) and its\n * content must be consistent with concatenation. Pre-mutation values are\n * used throughout — the caller (e.g. React) may already have written a new\n * value into a node before these records were processed.\n */\nfunction mergeCarrierFor(removed: Text, record: MutationRecord, now: number): Text | null {\n const carrier = record.previousSibling;\n if (!carrier || !(carrier instanceof Text)) return null;\n const carrierOriginalValue = recentOldValues.get(carrier)?.value;\n if (carrierOriginalValue === undefined) return null;\n const accumulated = (recentCarrierAccumulated.get(carrier)?.value ?? '') + snapshotValue(removed);\n if (!(carrier.nodeValue ?? '').startsWith(carrierOriginalValue + accumulated)) return null;\n recentCarrierAccumulated.set(carrier, { at: now, value: accumulated });\n return carrier;\n}\n\nfunction snapshotValue(node: Text): string {\n return recentOldValues.get(node)?.value ?? node.nodeValue ?? '';\n}\n\n/** Returns true if a displacement group was registered. */\nfunction handleDisplacedText(removed: Text, record: MutationRecord, now: number): boolean {\n const run = replacementRunFor(removed, record);\n if (run.length > 0) {\n const group: DisplacementGroup = {\n parent: record.target,\n originals: [{ node: removed, value: snapshotValue(removed) }, ...(pendingCarrierOriginals.get(removed) ?? [])],\n replacement: run,\n previousSiblingHint: record.previousSibling,\n nextSiblingHint: record.nextSibling,\n };\n pendingCarrierOriginals.delete(removed);\n registerGroup(group);\n return true;\n }\n\n const carrier = mergeCarrierFor(removed, record, now);\n if (carrier) {\n const carried = pendingCarrierOriginals.get(carrier) ?? [];\n carried.push({ node: removed, value: snapshotValue(removed) }, ...(pendingCarrierOriginals.get(removed) ?? []));\n pendingCarrierOriginals.delete(removed);\n pendingCarrierOriginals.set(carrier, carried);\n return false;\n }\n\n pendingOrphans.push({\n at: now,\n parent: record.target,\n removed,\n previousSibling: record.previousSibling,\n nextSibling: record.nextSibling,\n });\n return false;\n}\n\nfunction handleReplacementRemoved(group: DisplacementGroup, removed: Node, record: MutationRecord): void {\n groupByReplacementNode.delete(removed);\n const index = group.replacement.indexOf(removed);\n const run = replacementRunFor(removed, record).filter((node) => {\n // The translator may revert by re-inserting an original itself; an\n // original never doubles as its own group's replacement.\n return !group.originals.some((original) => original.node === node);\n });\n if (index >= 0) {\n group.replacement.splice(index, 1, ...run);\n } else {\n group.replacement.push(...run);\n }\n for (const node of run) groupByReplacementNode.set(node, group);\n\n // Full revert: every original is back in the document — the group is moot.\n if (group.replacement.length === 0 && group.originals.every((original) => original.node.parentNode !== null)) {\n unregisterGroup(group);\n return;\n }\n if (group.replacement.length === 0) {\n group.previousSiblingHint = record.previousSibling;\n group.nextSiblingHint = record.nextSibling;\n }\n}\n\n/**\n * Text removals with no replacement, seen around confirmed translator\n * activity, are translation deletions (word-order changes drop text nodes —\n * Chromium bug 872770). Track them so React can bring the text back.\n */\nfunction flushPendingOrphans(): void {\n for (const orphan of pendingOrphans) {\n if (displaced.has(orphan.removed)) continue;\n const group: DisplacementGroup = {\n parent: orphan.parent,\n originals: [\n { node: orphan.removed, value: snapshotValue(orphan.removed) },\n ...(pendingCarrierOriginals.get(orphan.removed) ?? []),\n ],\n replacement: [],\n previousSiblingHint: orphan.previousSibling,\n nextSiblingHint: orphan.nextSibling,\n };\n pendingCarrierOriginals.delete(orphan.removed);\n registerGroup(group);\n }\n pendingOrphans = [];\n}\n\nfunction processRecords(records: MutationRecord[]): void {\n if (records.length === 0) return;\n const now = performance.now();\n purgeExpired(now);\n\n let sawTranslatorActivity = false;\n for (const record of records) {\n if (record.type === 'childList') {\n for (const added of record.addedNodes) {\n if (added.nodeName === 'FONT') sawTranslatorActivity = true;\n if (record.nextSibling) {\n const entry = recentInsertedBefore.get(record.nextSibling) ?? { at: now, nodes: [] };\n entry.at = now;\n entry.nodes.push(added);\n recentInsertedBefore.set(record.nextSibling, entry);\n }\n }\n } else if (record.type === 'characterData' && record.oldValue !== null && !recentOldValues.has(record.target)) {\n recentOldValues.set(record.target, { at: now, value: record.oldValue });\n }\n }\n\n for (const record of records) {\n if (record.type !== 'childList') continue;\n for (const removed of record.removedNodes) {\n const group = groupByReplacementNode.get(removed);\n if (group) {\n handleReplacementRemoved(group, removed, record);\n } else if (removed instanceof Text && !displaced.has(removed)) {\n if (handleDisplacedText(removed, record, now)) sawTranslatorActivity = true;\n }\n }\n }\n\n if (sawTranslatorActivity) flushPendingOrphans();\n}\n\n/** Synchronously fold in records the observer hasn't delivered yet. */\nfunction drainPendingRecords(): void {\n if (observer) {\n const records = observer.takeRecords();\n guarded('record processing', () => processRecords(records), undefined);\n }\n}\n\ntype RestoreResult = 'restored' | 'gone' | 'untracked';\n\nfunction setTextValue(node: Text, value: string): void {\n domNatives().setData(node, value);\n}\n\n/**\n * Puts a displaced group's original text nodes back into the position its\n * replacement run occupies (removing the replacements), so the caller's\n * native DOM operation can proceed on a consistent tree.\n */\nfunction restoreGroup(group: DisplacementGroup, skipValueFor?: Text): RestoreResult {\n const natives = domNatives();\n unregisterGroup(group);\n for (const original of group.originals) {\n // Re-adoption makes the node's DOM state authoritative again; correlation\n // entries recorded while it was displaced would poison future sequences.\n recentOldValues.delete(original.node);\n recentCarrierAccumulated.delete(original.node);\n pendingCarrierOriginals.delete(original.node);\n }\n\n const attached = group.replacement.filter((node) => node.parentNode === group.parent);\n let cursor: Node | null;\n if (attached.length > 0) {\n cursor = attached[0] ?? null;\n } else if (group.previousSiblingHint?.parentNode === group.parent) {\n cursor = group.previousSiblingHint.nextSibling;\n } else if (group.nextSiblingHint?.parentNode === group.parent) {\n cursor = group.nextSiblingHint;\n } else if (group.replacement.length === 0 && group.parent.isConnected) {\n cursor = null; // deleted run with dead hints: append to the parent\n } else {\n return 'gone';\n }\n\n for (const original of group.originals) {\n if (original.node !== skipValueFor) setTextValue(original.node, original.value);\n if (original.node === cursor) {\n cursor = original.node.nextSibling;\n continue;\n }\n if (original.node.parentNode !== null && original.node.parentNode !== group.parent) continue;\n natives.insertBefore.call(group.parent, original.node, cursor);\n }\n for (const node of attached) {\n if (node.parentNode === group.parent && !group.originals.some((original) => original.node === node)) {\n natives.removeChild.call(group.parent, node);\n }\n }\n return 'restored';\n}\n\nfunction restoreDisplaced(node: Text, skipValue = false): RestoreResult {\n drainPendingRecords();\n const group = displaced.get(node);\n if (!group) return 'untracked';\n return restoreGroup(group, skipValue ? node : undefined);\n}\n\nlet uninstallCurrent: (() => void) | null = null;\n\nexport interface TranslationResilienceOptions {\n document?: Document;\n /** Observability hook: called with a short message on every non-native path taken. */\n onEvent?: (message: string) => void;\n}\n\nexport function installTranslationResilience(options: TranslationResilienceOptions = {}): () => void {\n if (uninstallCurrent) return uninstallCurrent;\n const natives = domNatives();\n const doc = options.document ?? document;\n emitEvent = options.onEvent ?? noopEvent;\n\n observer = new MutationObserver((records) => {\n guarded('record processing', () => processRecords(records), undefined);\n });\n observer.observe(doc, { childList: true, subtree: true, characterData: true, characterDataOldValue: true });\n\n Node.prototype.removeChild = function removeChild<T extends Node>(this: Node, child: T): T {\n if (child.parentNode !== this) {\n const outcome = guarded(\n 'removeChild repair',\n (): 'native' | 'handled' => {\n const result = child instanceof Text ? restoreDisplaced(child) : 'untracked';\n if (result === 'gone') {\n // The displaced content is already absent, which is what this removal wanted.\n emitEvent('removeChild: displaced text already gone, removal skipped');\n return 'handled';\n }\n if (result === 'untracked' && translationDetected) {\n // Translation moved this node somewhere we could not track (e.g. a\n // word-order change). Remove it from wherever it actually is.\n emitEvent('removeChild: removing node from its actual parent');\n if (child.parentNode) natives.removeChild.call(child.parentNode, child);\n return 'handled';\n }\n return 'native';\n },\n 'native'\n );\n if (outcome === 'handled') return child;\n }\n natives.removeChild.call(this, child);\n return child;\n };\n\n Node.prototype.insertBefore = function insertBefore<T extends Node>(this: Node, node: T, child: Node | null): T {\n if (node instanceof Text && node.parentNode === null) {\n guarded('insertBefore node repair', () => restoreDisplaced(node), 'untracked');\n }\n if (child && child.parentNode !== this) {\n const outcome = guarded(\n 'insertBefore reference repair',\n (): 'native' | 'handled' => {\n const result = child instanceof Text ? restoreDisplaced(child) : 'untracked';\n if (result !== 'restored' && translationDetected) {\n // The reference node is unrecoverable; appending keeps the new node\n // in the right parent, which is the best position still guaranteed.\n emitEvent('insertBefore: reference gone, appending instead');\n natives.appendChild.call(this, node);\n return 'handled';\n }\n return 'native';\n },\n 'native'\n );\n if (outcome === 'handled') return node;\n }\n natives.insertBefore.call(this, node, child);\n return node;\n };\n\n Node.prototype.appendChild = function appendChild<T extends Node>(this: Node, node: T): T {\n if (node instanceof Text && node.parentNode === null) {\n guarded('appendChild repair', () => restoreDisplaced(node), 'untracked');\n }\n natives.appendChild.call(this, node);\n return node;\n };\n\n const restoreAfterWrite = (node: Node): void => {\n if (node instanceof Text && node.parentNode === null) {\n guarded('text write repair', () => restoreDisplaced(node, true), 'untracked');\n }\n };\n\n Object.defineProperty(Node.prototype, 'nodeValue', {\n configurable: true,\n enumerable: natives.nodeValueDescriptor.enumerable,\n get: natives.nodeValueDescriptor.get,\n set(this: Node, value: string | null) {\n natives.setNodeValue(this, value);\n restoreAfterWrite(this);\n },\n });\n\n Object.defineProperty(CharacterData.prototype, 'data', {\n configurable: true,\n enumerable: natives.dataDescriptor.enumerable,\n get: natives.dataDescriptor.get,\n set(this: CharacterData, value: string) {\n natives.setData(this, value);\n restoreAfterWrite(this);\n },\n });\n\n uninstallCurrent = () => {\n observer?.disconnect();\n observer = null;\n translationDetected = false;\n emitEvent = noopEvent;\n clearCorrelationState();\n Node.prototype.removeChild = natives.removeChild;\n Node.prototype.insertBefore = natives.insertBefore;\n Node.prototype.appendChild = natives.appendChild;\n Object.defineProperty(Node.prototype, 'nodeValue', natives.nodeValueDescriptor);\n Object.defineProperty(CharacterData.prototype, 'data', natives.dataDescriptor);\n uninstallCurrent = null;\n };\n return uninstallCurrent;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/resilience.ts"],"sourcesContent":["export type { TranslationResilienceOptions } from './resilience';\nexport { installTranslationResilience } from './resilience';\n","/**\n * Makes React (and any other text-node-owning renderer) resilient to browser\n * page translation (Chrome / Google Translate), which merges and replaces\n * Text nodes with `<font>` wrappers. React keeps references to the original,\n * now detached, Text nodes, so without this shim:\n *\n * - unmounting translated conditional text throws NotFoundError (removeChild)\n * - mounting content before translated text throws NotFoundError (insertBefore)\n * - updating translated text writes to the detached node — the visible page\n * silently never updates again (see facebook/react#11538)\n *\n * Strategy — \"re-adoption\", not error swallowing:\n *\n * 1. A document-wide MutationObserver watches mutations. Translation displaces\n * text nodes in a recognizable pattern: adjacent Text nodes are merged\n * (`normalize()`), then the merged run is replaced by wrapper elements\n * inserted next to it in the same task. We track a DISPLACEMENT GROUP per\n * replaced run: the ordered renderer-owned originals (with their\n * pre-translation values) and the run of replacement nodes standing in for\n * them. React's own commits never look like this: React processes\n * deletions before placements, so a node it removes is never adjacent to a\n * same-batch insertion at removal time, and React never merges text nodes.\n *\n * 2. Patched Node.prototype.removeChild / insertBefore / appendChild and the\n * nodeValue / data setters detect operations on displaced Text nodes and\n * first RESTORE the group — original nodes go back into the replacement\n * run's position, replacements are removed — then let the native operation\n * proceed. This repairs the renderer's ownership invariant: updates become\n * visible, removals remove the right content, and the translator\n * re-translates the freshly restored text via its own observer.\n *\n * 3. Translation can also delete text nodes outright and move inline elements\n * (word-order changes — Chromium bug 872770). Deletions within a\n * translation batch get an empty group with position hints so the node can\n * come back if React updates it. Once translation activity has been\n * detected, unrecoverable parent mismatches degrade to guarded best-effort\n * operations instead of throwing.\n *\n * Before any translation activity is detected, operations on untracked nodes\n * behave exactly as before — including throwing on genuine bugs.\n */\n\ninterface DisplacedOriginal {\n node: Text;\n /** The node's value before translation touched it (normalize mutates the merge target). */\n value: string;\n}\n\ninterface DisplacementGroup {\n parent: Node;\n /** Renderer-owned text nodes this group stands in for, in document order. */\n originals: DisplacedOriginal[];\n /** Nodes currently displaying the originals' content (empty if translation deleted the run). */\n replacement: Node[];\n /** Position hints captured at removal time, used when `replacement` is empty. */\n previousSiblingHint: Node | null;\n nextSiblingHint: Node | null;\n}\n\nconst displaced = new WeakMap<Text, DisplacementGroup>();\nconst groupByReplacementNode = new WeakMap<Node, DisplacementGroup>();\n/** Live merge targets (normalize) carrying content of already-detached originals. */\nconst pendingCarrierOriginals = new WeakMap<Text, DisplacedOriginal[]>();\n\nlet observer: MutationObserver | null = null;\nlet translationDetected = false;\nconst noopEvent = (_message: string): void => undefined;\nlet emitEvent: (message: string) => void = noopEvent;\n\ninterface DomNatives {\n insertBefore: typeof Node.prototype.insertBefore;\n removeChild: typeof Node.prototype.removeChild;\n appendChild: typeof Node.prototype.appendChild;\n nodeValueDescriptor: PropertyDescriptor;\n dataDescriptor: PropertyDescriptor;\n setNodeValue(node: Node, value: string | null): void;\n setData(node: CharacterData, value: string): void;\n}\n\nlet capturedNatives: DomNatives | null = null;\n\n/**\n * Native DOM entry points, captured once on first use rather than at module\n * load so that importing this module is safe in non-DOM environments (SSR);\n * only calling install requires a browser.\n */\nfunction domNatives(): DomNatives {\n if (capturedNatives) return capturedNatives;\n if (typeof Node === 'undefined' || typeof CharacterData === 'undefined') {\n throw new Error(\n 'translation-resilience requires a DOM. Call installTranslationResilience() from client-side code only.'\n );\n }\n const nodeValueDescriptor = Object.getOwnPropertyDescriptor(Node.prototype, 'nodeValue');\n const dataDescriptor = Object.getOwnPropertyDescriptor(CharacterData.prototype, 'data');\n const nodeValueSet = nodeValueDescriptor?.set;\n const dataSet = dataDescriptor?.set;\n if (!nodeValueDescriptor?.get || !nodeValueSet || !dataDescriptor?.get || !dataSet) {\n throw new Error('translation-resilience: expected accessor descriptors for nodeValue and data');\n }\n capturedNatives = {\n insertBefore: Node.prototype.insertBefore,\n removeChild: Node.prototype.removeChild,\n appendChild: Node.prototype.appendChild,\n nodeValueDescriptor,\n dataDescriptor,\n setNodeValue: (node, value) => nodeValueSet.call(node, value),\n setData: (node, value) => dataSet.call(node, value),\n };\n return capturedNatives;\n}\n\n/**\n * A fault in the shim itself must never make things worse than stock\n * behavior: every non-native code path runs through this guard, and on an\n * internal error the caller falls back to the native operation.\n */\nfunction guarded<T>(operation: string, run: () => T, fallback: T): T {\n try {\n return run();\n } catch (error) {\n emitEvent(`internal error in ${operation}: ${error instanceof Error ? error.message : String(error)}`);\n return fallback;\n }\n}\n\nfunction registerGroup(group: DisplacementGroup): void {\n if (!translationDetected) {\n translationDetected = true;\n emitEvent('translation activity detected');\n }\n for (const original of group.originals) displaced.set(original.node, group);\n for (const node of group.replacement) groupByReplacementNode.set(node, group);\n}\n\nfunction unregisterGroup(group: DisplacementGroup): void {\n for (const original of group.originals) displaced.delete(original.node);\n for (const node of group.replacement) groupByReplacementNode.delete(node);\n}\n\n/**\n * Correlation state must survive batch boundaries: the record stream can be\n * split at arbitrary points — the shim's own synchronous drains inside\n * patched DOM methods split a translator's mutation sequence across several\n * processRecords calls, and translators themselves spread work across tasks.\n * Entries are consumed on use and expire after a short wall-clock window so\n * that old insertions are never attributed to unrelated removals later.\n */\nconst CORRELATION_WINDOW_MS = 100;\n\ninterface TimedRun {\n at: number;\n nodes: Node[];\n}\ninterface TimedValue {\n at: number;\n value: string;\n}\ninterface PendingOrphan {\n at: number;\n parent: Node;\n removed: Text;\n previousSibling: Node | null;\n nextSibling: Node | null;\n}\n\n/**\n * Every correlation store below is kept in ascending-`at` order: new entries\n * are appended, and any refresh of an existing entry's timestamp must\n * delete-then-set so the entry moves to the end. purgeExpired relies on this\n * to drop only the expired prefix and stop at the first fresh entry —\n * processRecords runs on every synchronous drain (i.e. inside patched DOM\n * calls), so a full scan of the stores per drain is quadratic under heavy\n * synchronous DOM churn.\n */\n\n/** Nodes recently inserted, indexed by their at-insertion-time nextSibling. */\nconst recentInsertedBefore = new Map<Node, TimedRun>();\n/** First recently-seen characterData oldValue per node = value before the mutation sequence. */\nconst recentOldValues = new Map<Node, TimedValue>();\n/** Accumulated merged-away content per normalize target, for validation. */\nconst recentCarrierAccumulated = new Map<Text, TimedValue>();\n/** Text removals with no replacement — become deletion groups once translator activity is confirmed. */\nlet pendingOrphans: PendingOrphan[] = [];\n\nfunction purgeExpired(now: number): void {\n for (const [key, entry] of recentInsertedBefore) {\n if (now - entry.at <= CORRELATION_WINDOW_MS) break;\n recentInsertedBefore.delete(key);\n }\n for (const [key, entry] of recentOldValues) {\n if (now - entry.at <= CORRELATION_WINDOW_MS) break;\n recentOldValues.delete(key);\n }\n for (const [key, entry] of recentCarrierAccumulated) {\n if (now - entry.at <= CORRELATION_WINDOW_MS) break;\n recentCarrierAccumulated.delete(key);\n }\n const firstFresh = pendingOrphans.findIndex((orphan) => now - orphan.at <= CORRELATION_WINDOW_MS);\n if (firstFresh === -1) {\n if (pendingOrphans.length > 0) pendingOrphans = [];\n } else if (firstFresh > 0) {\n pendingOrphans = pendingOrphans.slice(firstFresh);\n }\n}\n\nfunction clearCorrelationState(): void {\n recentInsertedBefore.clear();\n recentOldValues.clear();\n recentCarrierAccumulated.clear();\n pendingOrphans = [];\n}\n\n/** Replacement nodes a translator produces: <font> wrappers or plain text (revert). */\nfunction looksLikeTranslatorReplacement(node: Node): boolean {\n return node instanceof Text || node.nodeName === 'FONT';\n}\n\n/**\n * The run of nodes that took a removed node's place. A replaceChild-style\n * swap queues a single record carrying both sides — correlate those directly.\n * Translation instead inserts the wrapper(s) directly before the original and\n * then removes it as separate operations, so each wrapper's insertion record\n * has the original as its at-insertion-time nextSibling. Sibling pointers are\n * NOT walked at processing time — later mutations may already have\n * invalidated them.\n */\nfunction replacementRunFor(removed: Node, record: MutationRecord): Node[] {\n if (record.removedNodes.length === 1 && record.addedNodes.length === 1) {\n const added = record.addedNodes[0];\n return added ? [added] : [];\n }\n const entry = recentInsertedBefore.get(removed);\n if (!entry) return [];\n recentInsertedBefore.delete(removed);\n return entry.nodes.filter(looksLikeTranslatorReplacement);\n}\n\n/**\n * Detects a normalize() merge: the removed text's content was appended onto\n * the preceding Text sibling. The carrier must have a recent characterData\n * record (a renderer removing a node never mutates its neighbor) and its\n * content must be consistent with concatenation. Pre-mutation values are\n * used throughout — the caller (e.g. React) may already have written a new\n * value into a node before these records were processed.\n */\nfunction mergeCarrierFor(removed: Text, record: MutationRecord, now: number): Text | null {\n const carrier = record.previousSibling;\n if (!carrier || !(carrier instanceof Text)) return null;\n const carrierOriginalValue = recentOldValues.get(carrier)?.value;\n if (carrierOriginalValue === undefined) return null;\n const accumulated = (recentCarrierAccumulated.get(carrier)?.value ?? '') + snapshotValue(removed);\n if (!(carrier.nodeValue ?? '').startsWith(carrierOriginalValue + accumulated)) return null;\n // delete-then-set keeps the store in ascending-`at` order (see purgeExpired)\n recentCarrierAccumulated.delete(carrier);\n recentCarrierAccumulated.set(carrier, { at: now, value: accumulated });\n return carrier;\n}\n\nfunction snapshotValue(node: Text): string {\n return recentOldValues.get(node)?.value ?? node.nodeValue ?? '';\n}\n\n/** Returns true if a displacement group was registered. */\nfunction handleDisplacedText(removed: Text, record: MutationRecord, now: number): boolean {\n const run = replacementRunFor(removed, record);\n if (run.length > 0) {\n const group: DisplacementGroup = {\n parent: record.target,\n originals: [{ node: removed, value: snapshotValue(removed) }, ...(pendingCarrierOriginals.get(removed) ?? [])],\n replacement: run,\n previousSiblingHint: record.previousSibling,\n nextSiblingHint: record.nextSibling,\n };\n pendingCarrierOriginals.delete(removed);\n registerGroup(group);\n return true;\n }\n\n const carrier = mergeCarrierFor(removed, record, now);\n if (carrier) {\n const carried = pendingCarrierOriginals.get(carrier) ?? [];\n carried.push({ node: removed, value: snapshotValue(removed) }, ...(pendingCarrierOriginals.get(removed) ?? []));\n pendingCarrierOriginals.delete(removed);\n pendingCarrierOriginals.set(carrier, carried);\n return false;\n }\n\n pendingOrphans.push({\n at: now,\n parent: record.target,\n removed,\n previousSibling: record.previousSibling,\n nextSibling: record.nextSibling,\n });\n return false;\n}\n\nfunction handleReplacementRemoved(group: DisplacementGroup, removed: Node, record: MutationRecord): void {\n groupByReplacementNode.delete(removed);\n const index = group.replacement.indexOf(removed);\n const run = replacementRunFor(removed, record).filter((node) => {\n // The translator may revert by re-inserting an original itself; an\n // original never doubles as its own group's replacement.\n return !group.originals.some((original) => original.node === node);\n });\n if (index >= 0) {\n group.replacement.splice(index, 1, ...run);\n } else {\n group.replacement.push(...run);\n }\n for (const node of run) groupByReplacementNode.set(node, group);\n\n // Full revert: every original is back in the document — the group is moot.\n if (group.replacement.length === 0 && group.originals.every((original) => original.node.parentNode !== null)) {\n unregisterGroup(group);\n return;\n }\n if (group.replacement.length === 0) {\n group.previousSiblingHint = record.previousSibling;\n group.nextSiblingHint = record.nextSibling;\n }\n}\n\n/**\n * Text removals with no replacement, seen around confirmed translator\n * activity, are translation deletions (word-order changes drop text nodes —\n * Chromium bug 872770). Track them so React can bring the text back.\n */\nfunction flushPendingOrphans(): void {\n for (const orphan of pendingOrphans) {\n if (displaced.has(orphan.removed)) continue;\n const group: DisplacementGroup = {\n parent: orphan.parent,\n originals: [\n { node: orphan.removed, value: snapshotValue(orphan.removed) },\n ...(pendingCarrierOriginals.get(orphan.removed) ?? []),\n ],\n replacement: [],\n previousSiblingHint: orphan.previousSibling,\n nextSiblingHint: orphan.nextSibling,\n };\n pendingCarrierOriginals.delete(orphan.removed);\n registerGroup(group);\n }\n pendingOrphans = [];\n}\n\nfunction processRecords(records: MutationRecord[]): void {\n if (records.length === 0) return;\n const now = performance.now();\n purgeExpired(now);\n\n let sawTranslatorActivity = false;\n for (const record of records) {\n if (record.type === 'childList') {\n for (const added of record.addedNodes) {\n if (added.nodeName === 'FONT') sawTranslatorActivity = true;\n if (record.nextSibling) {\n const entry = recentInsertedBefore.get(record.nextSibling) ?? { at: now, nodes: [] };\n entry.at = now;\n entry.nodes.push(added);\n // delete-then-set keeps the store in ascending-`at` order (see purgeExpired)\n recentInsertedBefore.delete(record.nextSibling);\n recentInsertedBefore.set(record.nextSibling, entry);\n }\n }\n } else if (record.type === 'characterData' && record.oldValue !== null && !recentOldValues.has(record.target)) {\n recentOldValues.set(record.target, { at: now, value: record.oldValue });\n }\n }\n\n for (const record of records) {\n if (record.type !== 'childList') continue;\n for (const removed of record.removedNodes) {\n const group = groupByReplacementNode.get(removed);\n if (group) {\n handleReplacementRemoved(group, removed, record);\n } else if (removed instanceof Text && !displaced.has(removed)) {\n if (handleDisplacedText(removed, record, now)) sawTranslatorActivity = true;\n }\n }\n }\n\n if (sawTranslatorActivity) flushPendingOrphans();\n}\n\n/** Synchronously fold in records the observer hasn't delivered yet. */\nfunction drainPendingRecords(): void {\n if (observer) {\n const records = observer.takeRecords();\n guarded('record processing', () => processRecords(records), undefined);\n }\n}\n\ntype RestoreResult = 'restored' | 'gone' | 'untracked';\n\nfunction setTextValue(node: Text, value: string): void {\n domNatives().setData(node, value);\n}\n\n/**\n * Puts a displaced group's original text nodes back into the position its\n * replacement run occupies (removing the replacements), so the caller's\n * native DOM operation can proceed on a consistent tree.\n */\nfunction restoreGroup(group: DisplacementGroup, skipValueFor?: Text): RestoreResult {\n const natives = domNatives();\n unregisterGroup(group);\n for (const original of group.originals) {\n // Re-adoption makes the node's DOM state authoritative again; correlation\n // entries recorded while it was displaced would poison future sequences.\n recentOldValues.delete(original.node);\n recentCarrierAccumulated.delete(original.node);\n pendingCarrierOriginals.delete(original.node);\n }\n\n const attached = group.replacement.filter((node) => node.parentNode === group.parent);\n let cursor: Node | null;\n if (attached.length > 0) {\n cursor = attached[0] ?? null;\n } else if (group.previousSiblingHint?.parentNode === group.parent) {\n cursor = group.previousSiblingHint.nextSibling;\n } else if (group.nextSiblingHint?.parentNode === group.parent) {\n cursor = group.nextSiblingHint;\n } else if (group.replacement.length === 0 && group.parent.isConnected) {\n cursor = null; // deleted run with dead hints: append to the parent\n } else {\n return 'gone';\n }\n\n for (const original of group.originals) {\n if (original.node !== skipValueFor) setTextValue(original.node, original.value);\n if (original.node === cursor) {\n cursor = original.node.nextSibling;\n continue;\n }\n if (original.node.parentNode !== null && original.node.parentNode !== group.parent) continue;\n natives.insertBefore.call(group.parent, original.node, cursor);\n }\n for (const node of attached) {\n if (node.parentNode === group.parent && !group.originals.some((original) => original.node === node)) {\n natives.removeChild.call(group.parent, node);\n }\n }\n return 'restored';\n}\n\nfunction restoreDisplaced(node: Text, skipValue = false): RestoreResult {\n drainPendingRecords();\n const group = displaced.get(node);\n if (!group) return 'untracked';\n return restoreGroup(group, skipValue ? node : undefined);\n}\n\nlet uninstallCurrent: (() => void) | null = null;\n\nexport interface TranslationResilienceOptions {\n document?: Document;\n /** Observability hook: called with a short message on every non-native path taken. */\n onEvent?: (message: string) => void;\n}\n\nexport function installTranslationResilience(options: TranslationResilienceOptions = {}): () => void {\n if (uninstallCurrent) return uninstallCurrent;\n const natives = domNatives();\n const doc = options.document ?? document;\n emitEvent = options.onEvent ?? noopEvent;\n\n observer = new MutationObserver((records) => {\n guarded('record processing', () => processRecords(records), undefined);\n });\n observer.observe(doc, { childList: true, subtree: true, characterData: true, characterDataOldValue: true });\n\n Node.prototype.removeChild = function removeChild<T extends Node>(this: Node, child: T): T {\n if (child.parentNode !== this) {\n const outcome = guarded(\n 'removeChild repair',\n (): 'native' | 'handled' => {\n const result = child instanceof Text ? restoreDisplaced(child) : 'untracked';\n if (result === 'gone') {\n // The displaced content is already absent, which is what this removal wanted.\n emitEvent('removeChild: displaced text already gone, removal skipped');\n return 'handled';\n }\n if (result === 'untracked' && translationDetected) {\n // Translation moved this node somewhere we could not track (e.g. a\n // word-order change). Remove it from wherever it actually is.\n emitEvent('removeChild: removing node from its actual parent');\n if (child.parentNode) natives.removeChild.call(child.parentNode, child);\n return 'handled';\n }\n return 'native';\n },\n 'native'\n );\n if (outcome === 'handled') return child;\n }\n natives.removeChild.call(this, child);\n return child;\n };\n\n Node.prototype.insertBefore = function insertBefore<T extends Node>(this: Node, node: T, child: Node | null): T {\n if (node instanceof Text && node.parentNode === null) {\n guarded('insertBefore node repair', () => restoreDisplaced(node), 'untracked');\n }\n if (child && child.parentNode !== this) {\n const outcome = guarded(\n 'insertBefore reference repair',\n (): 'native' | 'handled' => {\n const result = child instanceof Text ? restoreDisplaced(child) : 'untracked';\n if (result !== 'restored' && translationDetected) {\n // The reference node is unrecoverable; appending keeps the new node\n // in the right parent, which is the best position still guaranteed.\n emitEvent('insertBefore: reference gone, appending instead');\n natives.appendChild.call(this, node);\n return 'handled';\n }\n return 'native';\n },\n 'native'\n );\n if (outcome === 'handled') return node;\n }\n natives.insertBefore.call(this, node, child);\n return node;\n };\n\n Node.prototype.appendChild = function appendChild<T extends Node>(this: Node, node: T): T {\n if (node instanceof Text && node.parentNode === null) {\n guarded('appendChild repair', () => restoreDisplaced(node), 'untracked');\n }\n natives.appendChild.call(this, node);\n return node;\n };\n\n const restoreAfterWrite = (node: Node): void => {\n if (node instanceof Text && node.parentNode === null) {\n guarded('text write repair', () => restoreDisplaced(node, true), 'untracked');\n }\n };\n\n Object.defineProperty(Node.prototype, 'nodeValue', {\n configurable: true,\n enumerable: natives.nodeValueDescriptor.enumerable,\n get: natives.nodeValueDescriptor.get,\n set(this: Node, value: string | null) {\n natives.setNodeValue(this, value);\n restoreAfterWrite(this);\n },\n });\n\n Object.defineProperty(CharacterData.prototype, 'data', {\n configurable: true,\n enumerable: natives.dataDescriptor.enumerable,\n get: natives.dataDescriptor.get,\n set(this: CharacterData, value: string) {\n natives.setData(this, value);\n restoreAfterWrite(this);\n },\n });\n\n uninstallCurrent = () => {\n observer?.disconnect();\n observer = null;\n translationDetected = false;\n emitEvent = noopEvent;\n clearCorrelationState();\n Node.prototype.removeChild = natives.removeChild;\n Node.prototype.insertBefore = natives.insertBefore;\n Node.prototype.appendChild = natives.appendChild;\n Object.defineProperty(Node.prototype, 'nodeValue', natives.nodeValueDescriptor);\n Object.defineProperty(CharacterData.prototype, 'data', natives.dataDescriptor);\n uninstallCurrent = null;\n };\n return uninstallCurrent;\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -59,15 +59,23 @@ var recentCarrierAccumulated = /* @__PURE__ */ new Map();
59
59
  var pendingOrphans = [];
60
60
  function purgeExpired(now) {
61
61
  for (const [key, entry] of recentInsertedBefore) {
62
- if (now - entry.at > CORRELATION_WINDOW_MS) recentInsertedBefore.delete(key);
62
+ if (now - entry.at <= CORRELATION_WINDOW_MS) break;
63
+ recentInsertedBefore.delete(key);
63
64
  }
64
65
  for (const [key, entry] of recentOldValues) {
65
- if (now - entry.at > CORRELATION_WINDOW_MS) recentOldValues.delete(key);
66
+ if (now - entry.at <= CORRELATION_WINDOW_MS) break;
67
+ recentOldValues.delete(key);
66
68
  }
67
69
  for (const [key, entry] of recentCarrierAccumulated) {
68
- if (now - entry.at > CORRELATION_WINDOW_MS) recentCarrierAccumulated.delete(key);
70
+ if (now - entry.at <= CORRELATION_WINDOW_MS) break;
71
+ recentCarrierAccumulated.delete(key);
72
+ }
73
+ const firstFresh = pendingOrphans.findIndex((orphan) => now - orphan.at <= CORRELATION_WINDOW_MS);
74
+ if (firstFresh === -1) {
75
+ if (pendingOrphans.length > 0) pendingOrphans = [];
76
+ } else if (firstFresh > 0) {
77
+ pendingOrphans = pendingOrphans.slice(firstFresh);
69
78
  }
70
- pendingOrphans = pendingOrphans.filter((orphan) => now - orphan.at <= CORRELATION_WINDOW_MS);
71
79
  }
72
80
  function clearCorrelationState() {
73
81
  recentInsertedBefore.clear();
@@ -96,6 +104,7 @@ function mergeCarrierFor(removed, record, now) {
96
104
  if (carrierOriginalValue === void 0) return null;
97
105
  const accumulated = ((_c = (_b = recentCarrierAccumulated.get(carrier)) == null ? void 0 : _b.value) != null ? _c : "") + snapshotValue(removed);
98
106
  if (!((_d = carrier.nodeValue) != null ? _d : "").startsWith(carrierOriginalValue + accumulated)) return null;
107
+ recentCarrierAccumulated.delete(carrier);
99
108
  recentCarrierAccumulated.set(carrier, { at: now, value: accumulated });
100
109
  return carrier;
101
110
  }
@@ -189,6 +198,7 @@ function processRecords(records) {
189
198
  const entry = (_a = recentInsertedBefore.get(record.nextSibling)) != null ? _a : { at: now, nodes: [] };
190
199
  entry.at = now;
191
200
  entry.nodes.push(added);
201
+ recentInsertedBefore.delete(record.nextSibling);
192
202
  recentInsertedBefore.set(record.nextSibling, entry);
193
203
  }
194
204
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/resilience.ts"],"sourcesContent":["/**\n * Makes React (and any other text-node-owning renderer) resilient to browser\n * page translation (Chrome / Google Translate), which merges and replaces\n * Text nodes with `<font>` wrappers. React keeps references to the original,\n * now detached, Text nodes, so without this shim:\n *\n * - unmounting translated conditional text throws NotFoundError (removeChild)\n * - mounting content before translated text throws NotFoundError (insertBefore)\n * - updating translated text writes to the detached node — the visible page\n * silently never updates again (see facebook/react#11538)\n *\n * Strategy — \"re-adoption\", not error swallowing:\n *\n * 1. A document-wide MutationObserver watches mutations. Translation displaces\n * text nodes in a recognizable pattern: adjacent Text nodes are merged\n * (`normalize()`), then the merged run is replaced by wrapper elements\n * inserted next to it in the same task. We track a DISPLACEMENT GROUP per\n * replaced run: the ordered renderer-owned originals (with their\n * pre-translation values) and the run of replacement nodes standing in for\n * them. React's own commits never look like this: React processes\n * deletions before placements, so a node it removes is never adjacent to a\n * same-batch insertion at removal time, and React never merges text nodes.\n *\n * 2. Patched Node.prototype.removeChild / insertBefore / appendChild and the\n * nodeValue / data setters detect operations on displaced Text nodes and\n * first RESTORE the group — original nodes go back into the replacement\n * run's position, replacements are removed — then let the native operation\n * proceed. This repairs the renderer's ownership invariant: updates become\n * visible, removals remove the right content, and the translator\n * re-translates the freshly restored text via its own observer.\n *\n * 3. Translation can also delete text nodes outright and move inline elements\n * (word-order changes — Chromium bug 872770). Deletions within a\n * translation batch get an empty group with position hints so the node can\n * come back if React updates it. Once translation activity has been\n * detected, unrecoverable parent mismatches degrade to guarded best-effort\n * operations instead of throwing.\n *\n * Before any translation activity is detected, operations on untracked nodes\n * behave exactly as before — including throwing on genuine bugs.\n */\n\ninterface DisplacedOriginal {\n node: Text;\n /** The node's value before translation touched it (normalize mutates the merge target). */\n value: string;\n}\n\ninterface DisplacementGroup {\n parent: Node;\n /** Renderer-owned text nodes this group stands in for, in document order. */\n originals: DisplacedOriginal[];\n /** Nodes currently displaying the originals' content (empty if translation deleted the run). */\n replacement: Node[];\n /** Position hints captured at removal time, used when `replacement` is empty. */\n previousSiblingHint: Node | null;\n nextSiblingHint: Node | null;\n}\n\nconst displaced = new WeakMap<Text, DisplacementGroup>();\nconst groupByReplacementNode = new WeakMap<Node, DisplacementGroup>();\n/** Live merge targets (normalize) carrying content of already-detached originals. */\nconst pendingCarrierOriginals = new WeakMap<Text, DisplacedOriginal[]>();\n\nlet observer: MutationObserver | null = null;\nlet translationDetected = false;\nconst noopEvent = (_message: string): void => undefined;\nlet emitEvent: (message: string) => void = noopEvent;\n\ninterface DomNatives {\n insertBefore: typeof Node.prototype.insertBefore;\n removeChild: typeof Node.prototype.removeChild;\n appendChild: typeof Node.prototype.appendChild;\n nodeValueDescriptor: PropertyDescriptor;\n dataDescriptor: PropertyDescriptor;\n setNodeValue(node: Node, value: string | null): void;\n setData(node: CharacterData, value: string): void;\n}\n\nlet capturedNatives: DomNatives | null = null;\n\n/**\n * Native DOM entry points, captured once on first use rather than at module\n * load so that importing this module is safe in non-DOM environments (SSR);\n * only calling install requires a browser.\n */\nfunction domNatives(): DomNatives {\n if (capturedNatives) return capturedNatives;\n if (typeof Node === 'undefined' || typeof CharacterData === 'undefined') {\n throw new Error(\n 'translation-resilience requires a DOM. Call installTranslationResilience() from client-side code only.'\n );\n }\n const nodeValueDescriptor = Object.getOwnPropertyDescriptor(Node.prototype, 'nodeValue');\n const dataDescriptor = Object.getOwnPropertyDescriptor(CharacterData.prototype, 'data');\n const nodeValueSet = nodeValueDescriptor?.set;\n const dataSet = dataDescriptor?.set;\n if (!nodeValueDescriptor?.get || !nodeValueSet || !dataDescriptor?.get || !dataSet) {\n throw new Error('translation-resilience: expected accessor descriptors for nodeValue and data');\n }\n capturedNatives = {\n insertBefore: Node.prototype.insertBefore,\n removeChild: Node.prototype.removeChild,\n appendChild: Node.prototype.appendChild,\n nodeValueDescriptor,\n dataDescriptor,\n setNodeValue: (node, value) => nodeValueSet.call(node, value),\n setData: (node, value) => dataSet.call(node, value),\n };\n return capturedNatives;\n}\n\n/**\n * A fault in the shim itself must never make things worse than stock\n * behavior: every non-native code path runs through this guard, and on an\n * internal error the caller falls back to the native operation.\n */\nfunction guarded<T>(operation: string, run: () => T, fallback: T): T {\n try {\n return run();\n } catch (error) {\n emitEvent(`internal error in ${operation}: ${error instanceof Error ? error.message : String(error)}`);\n return fallback;\n }\n}\n\nfunction registerGroup(group: DisplacementGroup): void {\n if (!translationDetected) {\n translationDetected = true;\n emitEvent('translation activity detected');\n }\n for (const original of group.originals) displaced.set(original.node, group);\n for (const node of group.replacement) groupByReplacementNode.set(node, group);\n}\n\nfunction unregisterGroup(group: DisplacementGroup): void {\n for (const original of group.originals) displaced.delete(original.node);\n for (const node of group.replacement) groupByReplacementNode.delete(node);\n}\n\n/**\n * Correlation state must survive batch boundaries: the record stream can be\n * split at arbitrary points — the shim's own synchronous drains inside\n * patched DOM methods split a translator's mutation sequence across several\n * processRecords calls, and translators themselves spread work across tasks.\n * Entries are consumed on use and expire after a short wall-clock window so\n * that old insertions are never attributed to unrelated removals later.\n */\nconst CORRELATION_WINDOW_MS = 100;\n\ninterface TimedRun {\n at: number;\n nodes: Node[];\n}\ninterface TimedValue {\n at: number;\n value: string;\n}\ninterface PendingOrphan {\n at: number;\n parent: Node;\n removed: Text;\n previousSibling: Node | null;\n nextSibling: Node | null;\n}\n\n/** Nodes recently inserted, indexed by their at-insertion-time nextSibling. */\nconst recentInsertedBefore = new Map<Node, TimedRun>();\n/** First recently-seen characterData oldValue per node = value before the mutation sequence. */\nconst recentOldValues = new Map<Node, TimedValue>();\n/** Accumulated merged-away content per normalize target, for validation. */\nconst recentCarrierAccumulated = new Map<Text, TimedValue>();\n/** Text removals with no replacement — become deletion groups once translator activity is confirmed. */\nlet pendingOrphans: PendingOrphan[] = [];\n\nfunction purgeExpired(now: number): void {\n for (const [key, entry] of recentInsertedBefore) {\n if (now - entry.at > CORRELATION_WINDOW_MS) recentInsertedBefore.delete(key);\n }\n for (const [key, entry] of recentOldValues) {\n if (now - entry.at > CORRELATION_WINDOW_MS) recentOldValues.delete(key);\n }\n for (const [key, entry] of recentCarrierAccumulated) {\n if (now - entry.at > CORRELATION_WINDOW_MS) recentCarrierAccumulated.delete(key);\n }\n pendingOrphans = pendingOrphans.filter((orphan) => now - orphan.at <= CORRELATION_WINDOW_MS);\n}\n\nfunction clearCorrelationState(): void {\n recentInsertedBefore.clear();\n recentOldValues.clear();\n recentCarrierAccumulated.clear();\n pendingOrphans = [];\n}\n\n/** Replacement nodes a translator produces: <font> wrappers or plain text (revert). */\nfunction looksLikeTranslatorReplacement(node: Node): boolean {\n return node instanceof Text || node.nodeName === 'FONT';\n}\n\n/**\n * The run of nodes that took a removed node's place. A replaceChild-style\n * swap queues a single record carrying both sides — correlate those directly.\n * Translation instead inserts the wrapper(s) directly before the original and\n * then removes it as separate operations, so each wrapper's insertion record\n * has the original as its at-insertion-time nextSibling. Sibling pointers are\n * NOT walked at processing time — later mutations may already have\n * invalidated them.\n */\nfunction replacementRunFor(removed: Node, record: MutationRecord): Node[] {\n if (record.removedNodes.length === 1 && record.addedNodes.length === 1) {\n const added = record.addedNodes[0];\n return added ? [added] : [];\n }\n const entry = recentInsertedBefore.get(removed);\n if (!entry) return [];\n recentInsertedBefore.delete(removed);\n return entry.nodes.filter(looksLikeTranslatorReplacement);\n}\n\n/**\n * Detects a normalize() merge: the removed text's content was appended onto\n * the preceding Text sibling. The carrier must have a recent characterData\n * record (a renderer removing a node never mutates its neighbor) and its\n * content must be consistent with concatenation. Pre-mutation values are\n * used throughout — the caller (e.g. React) may already have written a new\n * value into a node before these records were processed.\n */\nfunction mergeCarrierFor(removed: Text, record: MutationRecord, now: number): Text | null {\n const carrier = record.previousSibling;\n if (!carrier || !(carrier instanceof Text)) return null;\n const carrierOriginalValue = recentOldValues.get(carrier)?.value;\n if (carrierOriginalValue === undefined) return null;\n const accumulated = (recentCarrierAccumulated.get(carrier)?.value ?? '') + snapshotValue(removed);\n if (!(carrier.nodeValue ?? '').startsWith(carrierOriginalValue + accumulated)) return null;\n recentCarrierAccumulated.set(carrier, { at: now, value: accumulated });\n return carrier;\n}\n\nfunction snapshotValue(node: Text): string {\n return recentOldValues.get(node)?.value ?? node.nodeValue ?? '';\n}\n\n/** Returns true if a displacement group was registered. */\nfunction handleDisplacedText(removed: Text, record: MutationRecord, now: number): boolean {\n const run = replacementRunFor(removed, record);\n if (run.length > 0) {\n const group: DisplacementGroup = {\n parent: record.target,\n originals: [{ node: removed, value: snapshotValue(removed) }, ...(pendingCarrierOriginals.get(removed) ?? [])],\n replacement: run,\n previousSiblingHint: record.previousSibling,\n nextSiblingHint: record.nextSibling,\n };\n pendingCarrierOriginals.delete(removed);\n registerGroup(group);\n return true;\n }\n\n const carrier = mergeCarrierFor(removed, record, now);\n if (carrier) {\n const carried = pendingCarrierOriginals.get(carrier) ?? [];\n carried.push({ node: removed, value: snapshotValue(removed) }, ...(pendingCarrierOriginals.get(removed) ?? []));\n pendingCarrierOriginals.delete(removed);\n pendingCarrierOriginals.set(carrier, carried);\n return false;\n }\n\n pendingOrphans.push({\n at: now,\n parent: record.target,\n removed,\n previousSibling: record.previousSibling,\n nextSibling: record.nextSibling,\n });\n return false;\n}\n\nfunction handleReplacementRemoved(group: DisplacementGroup, removed: Node, record: MutationRecord): void {\n groupByReplacementNode.delete(removed);\n const index = group.replacement.indexOf(removed);\n const run = replacementRunFor(removed, record).filter((node) => {\n // The translator may revert by re-inserting an original itself; an\n // original never doubles as its own group's replacement.\n return !group.originals.some((original) => original.node === node);\n });\n if (index >= 0) {\n group.replacement.splice(index, 1, ...run);\n } else {\n group.replacement.push(...run);\n }\n for (const node of run) groupByReplacementNode.set(node, group);\n\n // Full revert: every original is back in the document — the group is moot.\n if (group.replacement.length === 0 && group.originals.every((original) => original.node.parentNode !== null)) {\n unregisterGroup(group);\n return;\n }\n if (group.replacement.length === 0) {\n group.previousSiblingHint = record.previousSibling;\n group.nextSiblingHint = record.nextSibling;\n }\n}\n\n/**\n * Text removals with no replacement, seen around confirmed translator\n * activity, are translation deletions (word-order changes drop text nodes —\n * Chromium bug 872770). Track them so React can bring the text back.\n */\nfunction flushPendingOrphans(): void {\n for (const orphan of pendingOrphans) {\n if (displaced.has(orphan.removed)) continue;\n const group: DisplacementGroup = {\n parent: orphan.parent,\n originals: [\n { node: orphan.removed, value: snapshotValue(orphan.removed) },\n ...(pendingCarrierOriginals.get(orphan.removed) ?? []),\n ],\n replacement: [],\n previousSiblingHint: orphan.previousSibling,\n nextSiblingHint: orphan.nextSibling,\n };\n pendingCarrierOriginals.delete(orphan.removed);\n registerGroup(group);\n }\n pendingOrphans = [];\n}\n\nfunction processRecords(records: MutationRecord[]): void {\n if (records.length === 0) return;\n const now = performance.now();\n purgeExpired(now);\n\n let sawTranslatorActivity = false;\n for (const record of records) {\n if (record.type === 'childList') {\n for (const added of record.addedNodes) {\n if (added.nodeName === 'FONT') sawTranslatorActivity = true;\n if (record.nextSibling) {\n const entry = recentInsertedBefore.get(record.nextSibling) ?? { at: now, nodes: [] };\n entry.at = now;\n entry.nodes.push(added);\n recentInsertedBefore.set(record.nextSibling, entry);\n }\n }\n } else if (record.type === 'characterData' && record.oldValue !== null && !recentOldValues.has(record.target)) {\n recentOldValues.set(record.target, { at: now, value: record.oldValue });\n }\n }\n\n for (const record of records) {\n if (record.type !== 'childList') continue;\n for (const removed of record.removedNodes) {\n const group = groupByReplacementNode.get(removed);\n if (group) {\n handleReplacementRemoved(group, removed, record);\n } else if (removed instanceof Text && !displaced.has(removed)) {\n if (handleDisplacedText(removed, record, now)) sawTranslatorActivity = true;\n }\n }\n }\n\n if (sawTranslatorActivity) flushPendingOrphans();\n}\n\n/** Synchronously fold in records the observer hasn't delivered yet. */\nfunction drainPendingRecords(): void {\n if (observer) {\n const records = observer.takeRecords();\n guarded('record processing', () => processRecords(records), undefined);\n }\n}\n\ntype RestoreResult = 'restored' | 'gone' | 'untracked';\n\nfunction setTextValue(node: Text, value: string): void {\n domNatives().setData(node, value);\n}\n\n/**\n * Puts a displaced group's original text nodes back into the position its\n * replacement run occupies (removing the replacements), so the caller's\n * native DOM operation can proceed on a consistent tree.\n */\nfunction restoreGroup(group: DisplacementGroup, skipValueFor?: Text): RestoreResult {\n const natives = domNatives();\n unregisterGroup(group);\n for (const original of group.originals) {\n // Re-adoption makes the node's DOM state authoritative again; correlation\n // entries recorded while it was displaced would poison future sequences.\n recentOldValues.delete(original.node);\n recentCarrierAccumulated.delete(original.node);\n pendingCarrierOriginals.delete(original.node);\n }\n\n const attached = group.replacement.filter((node) => node.parentNode === group.parent);\n let cursor: Node | null;\n if (attached.length > 0) {\n cursor = attached[0] ?? null;\n } else if (group.previousSiblingHint?.parentNode === group.parent) {\n cursor = group.previousSiblingHint.nextSibling;\n } else if (group.nextSiblingHint?.parentNode === group.parent) {\n cursor = group.nextSiblingHint;\n } else if (group.replacement.length === 0 && group.parent.isConnected) {\n cursor = null; // deleted run with dead hints: append to the parent\n } else {\n return 'gone';\n }\n\n for (const original of group.originals) {\n if (original.node !== skipValueFor) setTextValue(original.node, original.value);\n if (original.node === cursor) {\n cursor = original.node.nextSibling;\n continue;\n }\n if (original.node.parentNode !== null && original.node.parentNode !== group.parent) continue;\n natives.insertBefore.call(group.parent, original.node, cursor);\n }\n for (const node of attached) {\n if (node.parentNode === group.parent && !group.originals.some((original) => original.node === node)) {\n natives.removeChild.call(group.parent, node);\n }\n }\n return 'restored';\n}\n\nfunction restoreDisplaced(node: Text, skipValue = false): RestoreResult {\n drainPendingRecords();\n const group = displaced.get(node);\n if (!group) return 'untracked';\n return restoreGroup(group, skipValue ? node : undefined);\n}\n\nlet uninstallCurrent: (() => void) | null = null;\n\nexport interface TranslationResilienceOptions {\n document?: Document;\n /** Observability hook: called with a short message on every non-native path taken. */\n onEvent?: (message: string) => void;\n}\n\nexport function installTranslationResilience(options: TranslationResilienceOptions = {}): () => void {\n if (uninstallCurrent) return uninstallCurrent;\n const natives = domNatives();\n const doc = options.document ?? document;\n emitEvent = options.onEvent ?? noopEvent;\n\n observer = new MutationObserver((records) => {\n guarded('record processing', () => processRecords(records), undefined);\n });\n observer.observe(doc, { childList: true, subtree: true, characterData: true, characterDataOldValue: true });\n\n Node.prototype.removeChild = function removeChild<T extends Node>(this: Node, child: T): T {\n if (child.parentNode !== this) {\n const outcome = guarded(\n 'removeChild repair',\n (): 'native' | 'handled' => {\n const result = child instanceof Text ? restoreDisplaced(child) : 'untracked';\n if (result === 'gone') {\n // The displaced content is already absent, which is what this removal wanted.\n emitEvent('removeChild: displaced text already gone, removal skipped');\n return 'handled';\n }\n if (result === 'untracked' && translationDetected) {\n // Translation moved this node somewhere we could not track (e.g. a\n // word-order change). Remove it from wherever it actually is.\n emitEvent('removeChild: removing node from its actual parent');\n if (child.parentNode) natives.removeChild.call(child.parentNode, child);\n return 'handled';\n }\n return 'native';\n },\n 'native'\n );\n if (outcome === 'handled') return child;\n }\n natives.removeChild.call(this, child);\n return child;\n };\n\n Node.prototype.insertBefore = function insertBefore<T extends Node>(this: Node, node: T, child: Node | null): T {\n if (node instanceof Text && node.parentNode === null) {\n guarded('insertBefore node repair', () => restoreDisplaced(node), 'untracked');\n }\n if (child && child.parentNode !== this) {\n const outcome = guarded(\n 'insertBefore reference repair',\n (): 'native' | 'handled' => {\n const result = child instanceof Text ? restoreDisplaced(child) : 'untracked';\n if (result !== 'restored' && translationDetected) {\n // The reference node is unrecoverable; appending keeps the new node\n // in the right parent, which is the best position still guaranteed.\n emitEvent('insertBefore: reference gone, appending instead');\n natives.appendChild.call(this, node);\n return 'handled';\n }\n return 'native';\n },\n 'native'\n );\n if (outcome === 'handled') return node;\n }\n natives.insertBefore.call(this, node, child);\n return node;\n };\n\n Node.prototype.appendChild = function appendChild<T extends Node>(this: Node, node: T): T {\n if (node instanceof Text && node.parentNode === null) {\n guarded('appendChild repair', () => restoreDisplaced(node), 'untracked');\n }\n natives.appendChild.call(this, node);\n return node;\n };\n\n const restoreAfterWrite = (node: Node): void => {\n if (node instanceof Text && node.parentNode === null) {\n guarded('text write repair', () => restoreDisplaced(node, true), 'untracked');\n }\n };\n\n Object.defineProperty(Node.prototype, 'nodeValue', {\n configurable: true,\n enumerable: natives.nodeValueDescriptor.enumerable,\n get: natives.nodeValueDescriptor.get,\n set(this: Node, value: string | null) {\n natives.setNodeValue(this, value);\n restoreAfterWrite(this);\n },\n });\n\n Object.defineProperty(CharacterData.prototype, 'data', {\n configurable: true,\n enumerable: natives.dataDescriptor.enumerable,\n get: natives.dataDescriptor.get,\n set(this: CharacterData, value: string) {\n natives.setData(this, value);\n restoreAfterWrite(this);\n },\n });\n\n uninstallCurrent = () => {\n observer?.disconnect();\n observer = null;\n translationDetected = false;\n emitEvent = noopEvent;\n clearCorrelationState();\n Node.prototype.removeChild = natives.removeChild;\n Node.prototype.insertBefore = natives.insertBefore;\n Node.prototype.appendChild = natives.appendChild;\n Object.defineProperty(Node.prototype, 'nodeValue', natives.nodeValueDescriptor);\n Object.defineProperty(CharacterData.prototype, 'data', natives.dataDescriptor);\n uninstallCurrent = null;\n };\n return uninstallCurrent;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/resilience.ts"],"sourcesContent":["/**\n * Makes React (and any other text-node-owning renderer) resilient to browser\n * page translation (Chrome / Google Translate), which merges and replaces\n * Text nodes with `<font>` wrappers. React keeps references to the original,\n * now detached, Text nodes, so without this shim:\n *\n * - unmounting translated conditional text throws NotFoundError (removeChild)\n * - mounting content before translated text throws NotFoundError (insertBefore)\n * - updating translated text writes to the detached node — the visible page\n * silently never updates again (see facebook/react#11538)\n *\n * Strategy — \"re-adoption\", not error swallowing:\n *\n * 1. A document-wide MutationObserver watches mutations. Translation displaces\n * text nodes in a recognizable pattern: adjacent Text nodes are merged\n * (`normalize()`), then the merged run is replaced by wrapper elements\n * inserted next to it in the same task. We track a DISPLACEMENT GROUP per\n * replaced run: the ordered renderer-owned originals (with their\n * pre-translation values) and the run of replacement nodes standing in for\n * them. React's own commits never look like this: React processes\n * deletions before placements, so a node it removes is never adjacent to a\n * same-batch insertion at removal time, and React never merges text nodes.\n *\n * 2. Patched Node.prototype.removeChild / insertBefore / appendChild and the\n * nodeValue / data setters detect operations on displaced Text nodes and\n * first RESTORE the group — original nodes go back into the replacement\n * run's position, replacements are removed — then let the native operation\n * proceed. This repairs the renderer's ownership invariant: updates become\n * visible, removals remove the right content, and the translator\n * re-translates the freshly restored text via its own observer.\n *\n * 3. Translation can also delete text nodes outright and move inline elements\n * (word-order changes — Chromium bug 872770). Deletions within a\n * translation batch get an empty group with position hints so the node can\n * come back if React updates it. Once translation activity has been\n * detected, unrecoverable parent mismatches degrade to guarded best-effort\n * operations instead of throwing.\n *\n * Before any translation activity is detected, operations on untracked nodes\n * behave exactly as before — including throwing on genuine bugs.\n */\n\ninterface DisplacedOriginal {\n node: Text;\n /** The node's value before translation touched it (normalize mutates the merge target). */\n value: string;\n}\n\ninterface DisplacementGroup {\n parent: Node;\n /** Renderer-owned text nodes this group stands in for, in document order. */\n originals: DisplacedOriginal[];\n /** Nodes currently displaying the originals' content (empty if translation deleted the run). */\n replacement: Node[];\n /** Position hints captured at removal time, used when `replacement` is empty. */\n previousSiblingHint: Node | null;\n nextSiblingHint: Node | null;\n}\n\nconst displaced = new WeakMap<Text, DisplacementGroup>();\nconst groupByReplacementNode = new WeakMap<Node, DisplacementGroup>();\n/** Live merge targets (normalize) carrying content of already-detached originals. */\nconst pendingCarrierOriginals = new WeakMap<Text, DisplacedOriginal[]>();\n\nlet observer: MutationObserver | null = null;\nlet translationDetected = false;\nconst noopEvent = (_message: string): void => undefined;\nlet emitEvent: (message: string) => void = noopEvent;\n\ninterface DomNatives {\n insertBefore: typeof Node.prototype.insertBefore;\n removeChild: typeof Node.prototype.removeChild;\n appendChild: typeof Node.prototype.appendChild;\n nodeValueDescriptor: PropertyDescriptor;\n dataDescriptor: PropertyDescriptor;\n setNodeValue(node: Node, value: string | null): void;\n setData(node: CharacterData, value: string): void;\n}\n\nlet capturedNatives: DomNatives | null = null;\n\n/**\n * Native DOM entry points, captured once on first use rather than at module\n * load so that importing this module is safe in non-DOM environments (SSR);\n * only calling install requires a browser.\n */\nfunction domNatives(): DomNatives {\n if (capturedNatives) return capturedNatives;\n if (typeof Node === 'undefined' || typeof CharacterData === 'undefined') {\n throw new Error(\n 'translation-resilience requires a DOM. Call installTranslationResilience() from client-side code only.'\n );\n }\n const nodeValueDescriptor = Object.getOwnPropertyDescriptor(Node.prototype, 'nodeValue');\n const dataDescriptor = Object.getOwnPropertyDescriptor(CharacterData.prototype, 'data');\n const nodeValueSet = nodeValueDescriptor?.set;\n const dataSet = dataDescriptor?.set;\n if (!nodeValueDescriptor?.get || !nodeValueSet || !dataDescriptor?.get || !dataSet) {\n throw new Error('translation-resilience: expected accessor descriptors for nodeValue and data');\n }\n capturedNatives = {\n insertBefore: Node.prototype.insertBefore,\n removeChild: Node.prototype.removeChild,\n appendChild: Node.prototype.appendChild,\n nodeValueDescriptor,\n dataDescriptor,\n setNodeValue: (node, value) => nodeValueSet.call(node, value),\n setData: (node, value) => dataSet.call(node, value),\n };\n return capturedNatives;\n}\n\n/**\n * A fault in the shim itself must never make things worse than stock\n * behavior: every non-native code path runs through this guard, and on an\n * internal error the caller falls back to the native operation.\n */\nfunction guarded<T>(operation: string, run: () => T, fallback: T): T {\n try {\n return run();\n } catch (error) {\n emitEvent(`internal error in ${operation}: ${error instanceof Error ? error.message : String(error)}`);\n return fallback;\n }\n}\n\nfunction registerGroup(group: DisplacementGroup): void {\n if (!translationDetected) {\n translationDetected = true;\n emitEvent('translation activity detected');\n }\n for (const original of group.originals) displaced.set(original.node, group);\n for (const node of group.replacement) groupByReplacementNode.set(node, group);\n}\n\nfunction unregisterGroup(group: DisplacementGroup): void {\n for (const original of group.originals) displaced.delete(original.node);\n for (const node of group.replacement) groupByReplacementNode.delete(node);\n}\n\n/**\n * Correlation state must survive batch boundaries: the record stream can be\n * split at arbitrary points — the shim's own synchronous drains inside\n * patched DOM methods split a translator's mutation sequence across several\n * processRecords calls, and translators themselves spread work across tasks.\n * Entries are consumed on use and expire after a short wall-clock window so\n * that old insertions are never attributed to unrelated removals later.\n */\nconst CORRELATION_WINDOW_MS = 100;\n\ninterface TimedRun {\n at: number;\n nodes: Node[];\n}\ninterface TimedValue {\n at: number;\n value: string;\n}\ninterface PendingOrphan {\n at: number;\n parent: Node;\n removed: Text;\n previousSibling: Node | null;\n nextSibling: Node | null;\n}\n\n/**\n * Every correlation store below is kept in ascending-`at` order: new entries\n * are appended, and any refresh of an existing entry's timestamp must\n * delete-then-set so the entry moves to the end. purgeExpired relies on this\n * to drop only the expired prefix and stop at the first fresh entry —\n * processRecords runs on every synchronous drain (i.e. inside patched DOM\n * calls), so a full scan of the stores per drain is quadratic under heavy\n * synchronous DOM churn.\n */\n\n/** Nodes recently inserted, indexed by their at-insertion-time nextSibling. */\nconst recentInsertedBefore = new Map<Node, TimedRun>();\n/** First recently-seen characterData oldValue per node = value before the mutation sequence. */\nconst recentOldValues = new Map<Node, TimedValue>();\n/** Accumulated merged-away content per normalize target, for validation. */\nconst recentCarrierAccumulated = new Map<Text, TimedValue>();\n/** Text removals with no replacement — become deletion groups once translator activity is confirmed. */\nlet pendingOrphans: PendingOrphan[] = [];\n\nfunction purgeExpired(now: number): void {\n for (const [key, entry] of recentInsertedBefore) {\n if (now - entry.at <= CORRELATION_WINDOW_MS) break;\n recentInsertedBefore.delete(key);\n }\n for (const [key, entry] of recentOldValues) {\n if (now - entry.at <= CORRELATION_WINDOW_MS) break;\n recentOldValues.delete(key);\n }\n for (const [key, entry] of recentCarrierAccumulated) {\n if (now - entry.at <= CORRELATION_WINDOW_MS) break;\n recentCarrierAccumulated.delete(key);\n }\n const firstFresh = pendingOrphans.findIndex((orphan) => now - orphan.at <= CORRELATION_WINDOW_MS);\n if (firstFresh === -1) {\n if (pendingOrphans.length > 0) pendingOrphans = [];\n } else if (firstFresh > 0) {\n pendingOrphans = pendingOrphans.slice(firstFresh);\n }\n}\n\nfunction clearCorrelationState(): void {\n recentInsertedBefore.clear();\n recentOldValues.clear();\n recentCarrierAccumulated.clear();\n pendingOrphans = [];\n}\n\n/** Replacement nodes a translator produces: <font> wrappers or plain text (revert). */\nfunction looksLikeTranslatorReplacement(node: Node): boolean {\n return node instanceof Text || node.nodeName === 'FONT';\n}\n\n/**\n * The run of nodes that took a removed node's place. A replaceChild-style\n * swap queues a single record carrying both sides — correlate those directly.\n * Translation instead inserts the wrapper(s) directly before the original and\n * then removes it as separate operations, so each wrapper's insertion record\n * has the original as its at-insertion-time nextSibling. Sibling pointers are\n * NOT walked at processing time — later mutations may already have\n * invalidated them.\n */\nfunction replacementRunFor(removed: Node, record: MutationRecord): Node[] {\n if (record.removedNodes.length === 1 && record.addedNodes.length === 1) {\n const added = record.addedNodes[0];\n return added ? [added] : [];\n }\n const entry = recentInsertedBefore.get(removed);\n if (!entry) return [];\n recentInsertedBefore.delete(removed);\n return entry.nodes.filter(looksLikeTranslatorReplacement);\n}\n\n/**\n * Detects a normalize() merge: the removed text's content was appended onto\n * the preceding Text sibling. The carrier must have a recent characterData\n * record (a renderer removing a node never mutates its neighbor) and its\n * content must be consistent with concatenation. Pre-mutation values are\n * used throughout — the caller (e.g. React) may already have written a new\n * value into a node before these records were processed.\n */\nfunction mergeCarrierFor(removed: Text, record: MutationRecord, now: number): Text | null {\n const carrier = record.previousSibling;\n if (!carrier || !(carrier instanceof Text)) return null;\n const carrierOriginalValue = recentOldValues.get(carrier)?.value;\n if (carrierOriginalValue === undefined) return null;\n const accumulated = (recentCarrierAccumulated.get(carrier)?.value ?? '') + snapshotValue(removed);\n if (!(carrier.nodeValue ?? '').startsWith(carrierOriginalValue + accumulated)) return null;\n // delete-then-set keeps the store in ascending-`at` order (see purgeExpired)\n recentCarrierAccumulated.delete(carrier);\n recentCarrierAccumulated.set(carrier, { at: now, value: accumulated });\n return carrier;\n}\n\nfunction snapshotValue(node: Text): string {\n return recentOldValues.get(node)?.value ?? node.nodeValue ?? '';\n}\n\n/** Returns true if a displacement group was registered. */\nfunction handleDisplacedText(removed: Text, record: MutationRecord, now: number): boolean {\n const run = replacementRunFor(removed, record);\n if (run.length > 0) {\n const group: DisplacementGroup = {\n parent: record.target,\n originals: [{ node: removed, value: snapshotValue(removed) }, ...(pendingCarrierOriginals.get(removed) ?? [])],\n replacement: run,\n previousSiblingHint: record.previousSibling,\n nextSiblingHint: record.nextSibling,\n };\n pendingCarrierOriginals.delete(removed);\n registerGroup(group);\n return true;\n }\n\n const carrier = mergeCarrierFor(removed, record, now);\n if (carrier) {\n const carried = pendingCarrierOriginals.get(carrier) ?? [];\n carried.push({ node: removed, value: snapshotValue(removed) }, ...(pendingCarrierOriginals.get(removed) ?? []));\n pendingCarrierOriginals.delete(removed);\n pendingCarrierOriginals.set(carrier, carried);\n return false;\n }\n\n pendingOrphans.push({\n at: now,\n parent: record.target,\n removed,\n previousSibling: record.previousSibling,\n nextSibling: record.nextSibling,\n });\n return false;\n}\n\nfunction handleReplacementRemoved(group: DisplacementGroup, removed: Node, record: MutationRecord): void {\n groupByReplacementNode.delete(removed);\n const index = group.replacement.indexOf(removed);\n const run = replacementRunFor(removed, record).filter((node) => {\n // The translator may revert by re-inserting an original itself; an\n // original never doubles as its own group's replacement.\n return !group.originals.some((original) => original.node === node);\n });\n if (index >= 0) {\n group.replacement.splice(index, 1, ...run);\n } else {\n group.replacement.push(...run);\n }\n for (const node of run) groupByReplacementNode.set(node, group);\n\n // Full revert: every original is back in the document — the group is moot.\n if (group.replacement.length === 0 && group.originals.every((original) => original.node.parentNode !== null)) {\n unregisterGroup(group);\n return;\n }\n if (group.replacement.length === 0) {\n group.previousSiblingHint = record.previousSibling;\n group.nextSiblingHint = record.nextSibling;\n }\n}\n\n/**\n * Text removals with no replacement, seen around confirmed translator\n * activity, are translation deletions (word-order changes drop text nodes —\n * Chromium bug 872770). Track them so React can bring the text back.\n */\nfunction flushPendingOrphans(): void {\n for (const orphan of pendingOrphans) {\n if (displaced.has(orphan.removed)) continue;\n const group: DisplacementGroup = {\n parent: orphan.parent,\n originals: [\n { node: orphan.removed, value: snapshotValue(orphan.removed) },\n ...(pendingCarrierOriginals.get(orphan.removed) ?? []),\n ],\n replacement: [],\n previousSiblingHint: orphan.previousSibling,\n nextSiblingHint: orphan.nextSibling,\n };\n pendingCarrierOriginals.delete(orphan.removed);\n registerGroup(group);\n }\n pendingOrphans = [];\n}\n\nfunction processRecords(records: MutationRecord[]): void {\n if (records.length === 0) return;\n const now = performance.now();\n purgeExpired(now);\n\n let sawTranslatorActivity = false;\n for (const record of records) {\n if (record.type === 'childList') {\n for (const added of record.addedNodes) {\n if (added.nodeName === 'FONT') sawTranslatorActivity = true;\n if (record.nextSibling) {\n const entry = recentInsertedBefore.get(record.nextSibling) ?? { at: now, nodes: [] };\n entry.at = now;\n entry.nodes.push(added);\n // delete-then-set keeps the store in ascending-`at` order (see purgeExpired)\n recentInsertedBefore.delete(record.nextSibling);\n recentInsertedBefore.set(record.nextSibling, entry);\n }\n }\n } else if (record.type === 'characterData' && record.oldValue !== null && !recentOldValues.has(record.target)) {\n recentOldValues.set(record.target, { at: now, value: record.oldValue });\n }\n }\n\n for (const record of records) {\n if (record.type !== 'childList') continue;\n for (const removed of record.removedNodes) {\n const group = groupByReplacementNode.get(removed);\n if (group) {\n handleReplacementRemoved(group, removed, record);\n } else if (removed instanceof Text && !displaced.has(removed)) {\n if (handleDisplacedText(removed, record, now)) sawTranslatorActivity = true;\n }\n }\n }\n\n if (sawTranslatorActivity) flushPendingOrphans();\n}\n\n/** Synchronously fold in records the observer hasn't delivered yet. */\nfunction drainPendingRecords(): void {\n if (observer) {\n const records = observer.takeRecords();\n guarded('record processing', () => processRecords(records), undefined);\n }\n}\n\ntype RestoreResult = 'restored' | 'gone' | 'untracked';\n\nfunction setTextValue(node: Text, value: string): void {\n domNatives().setData(node, value);\n}\n\n/**\n * Puts a displaced group's original text nodes back into the position its\n * replacement run occupies (removing the replacements), so the caller's\n * native DOM operation can proceed on a consistent tree.\n */\nfunction restoreGroup(group: DisplacementGroup, skipValueFor?: Text): RestoreResult {\n const natives = domNatives();\n unregisterGroup(group);\n for (const original of group.originals) {\n // Re-adoption makes the node's DOM state authoritative again; correlation\n // entries recorded while it was displaced would poison future sequences.\n recentOldValues.delete(original.node);\n recentCarrierAccumulated.delete(original.node);\n pendingCarrierOriginals.delete(original.node);\n }\n\n const attached = group.replacement.filter((node) => node.parentNode === group.parent);\n let cursor: Node | null;\n if (attached.length > 0) {\n cursor = attached[0] ?? null;\n } else if (group.previousSiblingHint?.parentNode === group.parent) {\n cursor = group.previousSiblingHint.nextSibling;\n } else if (group.nextSiblingHint?.parentNode === group.parent) {\n cursor = group.nextSiblingHint;\n } else if (group.replacement.length === 0 && group.parent.isConnected) {\n cursor = null; // deleted run with dead hints: append to the parent\n } else {\n return 'gone';\n }\n\n for (const original of group.originals) {\n if (original.node !== skipValueFor) setTextValue(original.node, original.value);\n if (original.node === cursor) {\n cursor = original.node.nextSibling;\n continue;\n }\n if (original.node.parentNode !== null && original.node.parentNode !== group.parent) continue;\n natives.insertBefore.call(group.parent, original.node, cursor);\n }\n for (const node of attached) {\n if (node.parentNode === group.parent && !group.originals.some((original) => original.node === node)) {\n natives.removeChild.call(group.parent, node);\n }\n }\n return 'restored';\n}\n\nfunction restoreDisplaced(node: Text, skipValue = false): RestoreResult {\n drainPendingRecords();\n const group = displaced.get(node);\n if (!group) return 'untracked';\n return restoreGroup(group, skipValue ? node : undefined);\n}\n\nlet uninstallCurrent: (() => void) | null = null;\n\nexport interface TranslationResilienceOptions {\n document?: Document;\n /** Observability hook: called with a short message on every non-native path taken. */\n onEvent?: (message: string) => void;\n}\n\nexport function installTranslationResilience(options: TranslationResilienceOptions = {}): () => void {\n if (uninstallCurrent) return uninstallCurrent;\n const natives = domNatives();\n const doc = options.document ?? document;\n emitEvent = options.onEvent ?? noopEvent;\n\n observer = new MutationObserver((records) => {\n guarded('record processing', () => processRecords(records), undefined);\n });\n observer.observe(doc, { childList: true, subtree: true, characterData: true, characterDataOldValue: true });\n\n Node.prototype.removeChild = function removeChild<T extends Node>(this: Node, child: T): T {\n if (child.parentNode !== this) {\n const outcome = guarded(\n 'removeChild repair',\n (): 'native' | 'handled' => {\n const result = child instanceof Text ? restoreDisplaced(child) : 'untracked';\n if (result === 'gone') {\n // The displaced content is already absent, which is what this removal wanted.\n emitEvent('removeChild: displaced text already gone, removal skipped');\n return 'handled';\n }\n if (result === 'untracked' && translationDetected) {\n // Translation moved this node somewhere we could not track (e.g. a\n // word-order change). Remove it from wherever it actually is.\n emitEvent('removeChild: removing node from its actual parent');\n if (child.parentNode) natives.removeChild.call(child.parentNode, child);\n return 'handled';\n }\n return 'native';\n },\n 'native'\n );\n if (outcome === 'handled') return child;\n }\n natives.removeChild.call(this, child);\n return child;\n };\n\n Node.prototype.insertBefore = function insertBefore<T extends Node>(this: Node, node: T, child: Node | null): T {\n if (node instanceof Text && node.parentNode === null) {\n guarded('insertBefore node repair', () => restoreDisplaced(node), 'untracked');\n }\n if (child && child.parentNode !== this) {\n const outcome = guarded(\n 'insertBefore reference repair',\n (): 'native' | 'handled' => {\n const result = child instanceof Text ? restoreDisplaced(child) : 'untracked';\n if (result !== 'restored' && translationDetected) {\n // The reference node is unrecoverable; appending keeps the new node\n // in the right parent, which is the best position still guaranteed.\n emitEvent('insertBefore: reference gone, appending instead');\n natives.appendChild.call(this, node);\n return 'handled';\n }\n return 'native';\n },\n 'native'\n );\n if (outcome === 'handled') return node;\n }\n natives.insertBefore.call(this, node, child);\n return node;\n };\n\n Node.prototype.appendChild = function appendChild<T extends Node>(this: Node, node: T): T {\n if (node instanceof Text && node.parentNode === null) {\n guarded('appendChild repair', () => restoreDisplaced(node), 'untracked');\n }\n natives.appendChild.call(this, node);\n return node;\n };\n\n const restoreAfterWrite = (node: Node): void => {\n if (node instanceof Text && node.parentNode === null) {\n guarded('text write repair', () => restoreDisplaced(node, true), 'untracked');\n }\n };\n\n Object.defineProperty(Node.prototype, 'nodeValue', {\n configurable: true,\n enumerable: natives.nodeValueDescriptor.enumerable,\n get: natives.nodeValueDescriptor.get,\n set(this: Node, value: string | null) {\n natives.setNodeValue(this, value);\n restoreAfterWrite(this);\n },\n });\n\n Object.defineProperty(CharacterData.prototype, 'data', {\n configurable: true,\n enumerable: natives.dataDescriptor.enumerable,\n get: natives.dataDescriptor.get,\n set(this: CharacterData, value: string) {\n natives.setData(this, value);\n restoreAfterWrite(this);\n },\n });\n\n uninstallCurrent = () => {\n observer?.disconnect();\n observer = null;\n translationDetected = false;\n emitEvent = noopEvent;\n clearCorrelationState();\n Node.prototype.removeChild = natives.removeChild;\n Node.prototype.insertBefore = natives.insertBefore;\n Node.prototype.appendChild = natives.appendChild;\n Object.defineProperty(Node.prototype, 'nodeValue', natives.nodeValueDescriptor);\n Object.defineProperty(CharacterData.prototype, 'data', natives.dataDescriptor);\n uninstallCurrent = null;\n };\n return uninstallCurrent;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "translation-resilience",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Stops browser page translation (Google Translate) from crashing React and silently freezing translated text, by re-adopting displaced text nodes instead of swallowing errors.",
5
5
  "keywords": [
6
6
  "google-translate",