Skip to content

fix: guard customElements.define against double-registration#28

Open
dmchaledev wants to merge 1 commit into
mainfrom
claude/sleepy-rubin-hjj7dr
Open

fix: guard customElements.define against double-registration#28
dmchaledev wants to merge 1 commit into
mainfrom
claude/sleepy-rubin-hjj7dr

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Problem

The element is registered unconditionally at the bottom of the module:

customElements.define('hailbytes-vuln-calculator', HailbytesVulnCalculator);

CustomElementRegistry.define() throws a DOMException if the name is already
registered. So if this module is evaluated more than once on the same page,
the second evaluation throws an uncaught error that aborts the rest of that
script and can break the host page.

This is a realistic scenario for a distributable web component:

  • A site loads the component from a CDN <script type="module"> and also
    pulls in a bundled copy via a framework dependency.
  • Two independently-bundled widgets on the same page each include the component.
  • A CDN URL is referenced both pinned and via @main/@latest, resolving to
    two module instances.

The failure mode is silent in dev (one copy) and only surfaces in the field,
which makes it especially worth guarding at the source.

Fix

Register only when the name isn't already taken — the standard defensive
pattern for shippable custom elements:

if (!customElements.get('hailbytes-vuln-calculator')) {
  customElements.define('hailbytes-vuln-calculator', HailbytesVulnCalculator);
}

Test

Adds test/define-guard.test.mjs, which installs a customElements shim that
throws on duplicate define() (mirroring browser behavior) and then imports the
module twice (using a query-string suffix to force a second ESM evaluation). The
test fails against the old unguarded code and passes with the guard.

Verified locally:

  • Without the fix: # pass 0 / # fail 1
  • With the fix: full suite # tests 66 / # pass 66 / # fail 0

Scope

Single-file behavior change plus one test and a CHANGELOG entry. No public API,
output shape, or calculation change.

🤖 Generated with Claude Code


Generated by Claude Code

Loading the module more than once on the same page (a CDN <script> plus a
bundled copy, or two independently bundled widgets) called define() with an
already-used name, throwing an uncaught DOMException that breaks the host
page. Register the element only when it isn't already defined.

Adds test/define-guard.test.mjs, which re-imports the module with a registry
shim that throws on duplicate define (mirroring the browser) to lock in the
guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7BWmqwjZtKT4VbeAWGqh7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants