Extracting Delivery Information from Dynamically Rendered HTML
TIL that a content script querying the DOM immediately on page load found nothing, because the POS's order details render client-side, after the content script's own 'document_idle' point had already passed.
The Problem #
The extension's content script ran at document_idle, queried the DOM for order details, and consistently found nothing — not intermittently, every single time — despite the data clearly being visible on screen a moment later.
Context #
The POS is a client-rendered SPA. Its own JavaScript fetches order data and paints it into the DOM after the initial page load, meaning "the page has loaded" and "the data I need exists in the DOM" are two different points in time.
What I Tried #
Added a fixed setTimeout delay before querying the DOM, on the theory that a couple of seconds would be enough for the POS to finish rendering.
What Went Wrong #
It worked on my machine and failed for some users — because a fixed timeout is a bet on how fast the POS's own data fetch and render happens, and that varies with network conditions and server load in ways I don't control from the extension side.
The Solution #
Replaced the fixed delay with a MutationObserver watching for the specific element the extraction depends on, so the extraction runs exactly when the data actually appears rather than after a guessed delay.
function waitForOrderElement() {
return new Promise((resolve) => {
const existing = document.querySelector("[data-order-id]");
if (existing) return resolve(existing);
const observer = new MutationObserver(() => {
const el = document.querySelector("[data-order-id]");
if (el) {
observer.disconnect();
resolve(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
});
}Why It Works #
A MutationObserver reacts to the actual DOM change instead of guessing when it'll happen, which removes the timing assumption entirely — it works the same whether the POS renders in 200ms or 3 seconds, because it's driven by the event, not a clock.
Lessons Learned #
document_idle describes when the content script itself is ready to run, not when the host page's own data has finished loading — those are unrelated timelines for a client-rendered SPA, and conflating them is what made the fixed-delay approach unreliable.
What I Would Do Differently #
I'd default to observer-based waiting for any DOM-dependent extraction against a client-rendered page from the start, rather than reaching for a timeout as the first (and, it turned out, unreliable) instinct.
Related Concepts #
MutationObserver, content script injection timing (document_start/document_end/document_idle), race conditions against client-rendered pages.
Related content
Writing a Reliable Regex for Extracting Delivery Times
TIL that a regex tuned against a handful of examples ('DELIVERY TIME: 2:00 PM') broke on real data within a day because of extra whitespace and inconsistent AM/PM casing I hadn't accounted for.
Injecting Custom UI Into a Website You Don't Own
TIL that a naive injected panel kept getting wiped out by the host page's own re-renders — the fix was watching for that, not fighting it, and mounting into a container the host page has no reason to touch.
Reading Data from an Existing Web App with a Chrome Content Script
TIL that a content script reading order data straight from the DOM breaks every time the POS ships a markup change — the fix was reading from the most stable layer available, not the most convenient one.