The MutationObserver Error That Only Fired Because the Script Ran Too Early

A script that was moved to run earlier on the page started throwing an error it had never thrown before. The fix that had made it "run early" and the bug that made it crash were the same change.

Why the script needed to run early at all

The Superfish mobile-menu accessibility fix — an aria-label and visible text added to a menu toggle button — only works if it runs after the toggle button exists in the DOM. Rather than wait for a page-load event, the script was given the highest priority Drupal's render pipeline supports for head-attached content: '#weight' => -9998, second only to Google Tag Manager itself. The stated reason, in the commit that set it: making sure the fix "executes early for Agentic Browsing" — early enough to be present before whatever crawling or auditing check was inspecting the page.

What early actually meant

A negative weight in a #attached render array controls order within <head>, and <head> is rendered before <body> exists at all. The script's own fallback logic anticipated the toggle button not being there yet — it tries to find the button immediately, and if that fails, sets up a MutationObserver to watch for it being added later:

  • observer.observe(document.body, {childList:true,subtree:true});

That line assumes document.body exists by the time it runs. Moving the whole script earlier in <head> didn't just make the toggle-detection race more likely to need the fallback path — it made the fallback path itself fail, because at weight -9998, the script could execute before the parser had reached <body> at all. document.body was null. Calling .observe(null, ...) throws.

The actual error, and the actual fix

The error was "parameter 1 is not of type Node" — the browser's way of saying the first argument to observe() wasn't a DOM node, because it wasn't anything. The fix, one line: if(document.body) observer.observe(document.body, {childList:true,subtree:true});. If document.body doesn't exist yet, skip setting up the observer for now — which sounds like it would break the fallback entirely, except the script's own outer logic already handles this: the whole block only runs if the immediate lookup for the toggle button fails, and if the guard also skips setting up the observer, the fix simply doesn't apply on that particular page load. Not silently correct, but not throwing an uncaught error into the console either.

The part worth naming

This wasn't a bug that existed in the script and got exposed by unrelated circumstances. It was created directly by the change meant to fix a different problem — the priority bump was the whole reason document.body could be missing when the script ran. Every fix for "make X happen earlier" carries this same risk implicitly: earlier means fewer guarantees about what else has happened yet, and a script written assuming a normal document-ready timeline doesn't automatically know it's now running at a point where assumptions like "the body tag exists" can be false. Nothing about the original script was wrong for its original context. The context changed out from under it.

Why if(document.body) is the correct minimal fix, not a workaround

The temptation with a fix like this is to treat it as a symptom patch — check for null, move on, don't ask why null was possible in the first place. Here it's actually the right amount of fix: the underlying reason document.body can be missing (a deliberately aggressive #weight value chosen for a specific, separate reason) isn't itself a mistake worth reversing. The script's whole purpose already includes handling "the button doesn't exist yet" as a normal case, via the observer. Extending that same tolerance to "the body element doesn't exist yet either" is consistent with the script's own design, not a patch bolted on top of it. The lesson isn't "don't run things early" — it's that running something earlier changes its assumptions, and the fix for that is checking the assumption, not reverting the timing decision that exposed it.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Please share this article on your favorite website or platform.