Skip to content

deka1105/Xpath_Patcher

Repository files navigation

XPath Patcher — Changelog


v0.2 — Dynamic Element Watching

File changed: content.js only. All other files unchanged.

What changed

❌ Removed: Fixed setTimeout delays

// v0.1 — blind wait, regardless of whether element is ready
run();
setTimeout(run, 500); // on SPA nav

Hardcoded delays were guesswork. Too short → patch fires before element exists. Too long → visible flicker as the original text shows before being replaced.


✅ Added: Per-rule watchRule() function

Each rule now gets its own independent two-phase lifecycle:

Phase 1 — Seeker observer If the target element isn't in the DOM yet, a MutationObserver watches the entire document and fires tryPatch() on every DOM change. As soon as the element appears, it patches immediately and transitions to Phase 2.

// v0.2 — fires the instant the element lands in the DOM
const seeker = new MutationObserver(() => {
  if (!tryPatch()) return; // not there yet, keep watching
  seeker.disconnect();     // found it — hand off to guard
  // ... attach guard observer
});

Phase 2 — Guard observer After the initial patch, a second observer keeps watching in case the page's own JavaScript (React, Vue, etc.) re-renders and overwrites the patched text. It re-applies the patch immediately whenever that happens.

// v0.2 — re-patches whenever the framework clobbers our change
const guard = new MutationObserver(() => {
  if (guardActive) return; // skip mutations we caused ourselves
  tryPatch();
});

✅ Added: Mutation guard flag (guardActive)

Prevents infinite loops of the form:
patch DOM → mutation fires → patch DOM → mutation fires → …

When tryPatch() runs, it sets guardActive = true immediately. The guard observer skips any mutations while the flag is set. A requestAnimationFrame clears the flag after the browser has finished processing our write, so future legitimate re-renders by the framework are still caught.

// v0.1 — no guard, potential for thrash loops
node.nodeValue = rule.text;

// v0.2 — guarded write
guardActive = true;
patchNodes(nodes, rule.text);
requestAnimationFrame(() => { guardActive = false; });

✅ Added: No-op check before patching

patchNodes() now checks whether the value is already correct before writing, avoiding unnecessary DOM mutations.

// v0.1
node.nodeValue = rule.text; // always writes

// v0.2
if (node.nodeValue !== rule.text) { node.nodeValue = rule.text; } // skip if already correct

✅ Added: 10s deadline for seeker observer

If a target element never appears (wrong XPath, page didn't load it, etc.), the seeker disconnects after MAX_WAIT_MS = 10000 to avoid leaking observers.

const deadline = Date.now() + MAX_WAIT_MS;
const seeker = new MutationObserver(() => {
  if (Date.now() > deadline) {
    seeker.disconnect();
    console.warn(`[XPath Patcher] ✗ Gave up waiting after ${MAX_WAIT_MS}ms`);
    return;
  }
  // ...
});

✅ Added: Version tag in all log messages

All console logs now include the version for easier debugging when comparing behaviour across installs.

// v0.1
[XPath Patcher] ✓ Patched 1 node(s) at "//h1"

// v0.2
[XPath Patcher v0.2] ✓ "//h1" → "My Custom Title"
[XPath Patcher v0.2] ⏳ Waiting for element: "//div[@class='price']"
[XPath Patcher v0.2] URL changed → re-evaluating rules

Summary table

Behaviour v0.1 v0.2
Wait strategy Fixed setTimeout DOM-event driven
Patch timing After arbitrary delay Instant when element appears
Re-render protection None Guard observer
Infinite loop protection None guardActive flag + requestAnimationFrame
Stale observer cleanup None 10s deadline on seeker
Per-rule independence No (shared single run()) Yes (each rule has its own observers)
Version in logs No Yes

About

Replace text at XPath locations based on rules stored in Google Sheets.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages