Skip to content

fix: <vimeo-video> element race condition#220

Open
spuppo-mux wants to merge 9 commits intomuxinc:mainfrom
spuppo-mux:fix/vimeo-video-element-race-condition
Open

fix: <vimeo-video> element race condition#220
spuppo-mux wants to merge 9 commits intomuxinc:mainfrom
spuppo-mux:fix/vimeo-video-element-race-condition

Conversation

@spuppo-mux
Copy link
Copy Markdown
Contributor

@spuppo-mux spuppo-mux commented Mar 23, 2026

Closes #219

Problem

Sometimes when loading Vimeo Player (e.g. on our example switching TikTok → Vimeo) the Vimeo iframe
loaded with default styling instead of the configured color, because the config value was not reaching the iframe URL.

Reproduction: switch to TikTok then back to Vimeo on the Next.js example. The bug manifests as the default Vimeo blue instead of the configured pink. As this is a race condition you may need to switch multiple times for it to manifest.

How our example should look:
Screenshot 2026-03-20 at 5 29 43 PM
How it looks when the bug happens:
Screenshot 2026-03-20 at 5 30 19 PM

Root cause

By checking logs I saw that when the bug happened, we were getting into the isInit === false case (and load() was running just one time), so the options weren't being applied.

Digging further, two distinct issues were combined under the "race condition":

  1. config setter never triggered a reload. Setting config after the element had already loaded simply mutated the private field — there was nothing to push the new value into the iframe.
  2. Stale shadow DOM on re-attach. When the element was disconnected and re-attached, the shadow DOM survived. On the next load() we hit the isFirstLoad && iframe branch, read the previous mount's data-config back into #config, and then reused the old iframe — so any new config set before re-attach was silently overwritten.

What was done to address it

  • config setter now calls this.load() (debounced via the existing tick in load() so it batches with concurrent src / attribute changes). A structural-equality short-circuit avoids redundant reloads when React re-renders
    with an equivalent config object.
  • Three-way init branching in load() that distinguishes the cases of: no shadow DOM yet (first mount → build), shadow DOM survived a disconnect (remount → rebuild template so the iframe URL reflects current src/config),
    and shadow DOM came from SSR declarative shadow DOM (hydration → adopt the existing iframe; its URL is already correct and rebuilding would destroy the in-flight request, causing a visible stutter).
  • #wasDisconnected flag set in disconnectedCallback and cleared inside load() — this is what lets the SSR-hydration and remount paths be told apart.
  • connectedCallback re-triggers load() when re-attaching, since attributes don't change on re-attach and otherwise wouldn't fire attributeChangedCallback.
  • disconnectedCallback clears #loadRequested, #hasLoaded, #isInit, resets loadComplete, and destroys the old Vimeo API instance.

Extra changes (review feedback / edge cases)

  • Restored loadComplete = new PublicPromise() to the original timing — earlier it had been moved before the tick, which (as flagged in review) could let handlers awaiting loadComplete resolve prematurely against the previous
    load's promise. It now also runs after the early return for missing src, so a config-set-before-src no longer orphans an unresolved promise.
  • Old Vimeo API instances are destroy()'d on reload and on disconnect to prevent leaks of stale player instances and listeners.
  • Code clarity:
    • Moved API listener setup into a new #setupApiListeners method.
    • Moved the #onLoaded handler to a class method, since it's used from two places.
    • Restructured the if/else at the bottom of load for readability.

Note

Medium Risk
Changes the <vimeo-video> element’s load/lifecycle behavior (including iframe reuse vs rebuild and API teardown), which could affect playback initialization timing and SSR hydration behavior if edge cases weren’t covered.

Overview
Fixes a race where config/embed options could be missed by reworking load() to debounce consistently, avoid orphaning existing loadComplete when src is empty, and reload when config actually changes.

Adds explicit mount/unmount handling: on disconnect it resets internal load/init state and destroys the Vimeo API instance, and on reconnect it triggers a reload when the element was remounted.

Improves SSR declarative shadow DOM hydration by adopting an existing iframe (and recovering config from data-config) instead of always rebuilding the iframe, while still rebuilding on true remounts so the iframe URL reflects current src/config.

Reviewed by Cursor Bugbot for commit 323907f. Bugbot is set up for automated code reviews on this repo. Configure here.

@snyk-io
Copy link
Copy Markdown

snyk-io Bot commented Mar 23, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 23, 2026

@spuppo-mux is attempting to deploy a commit to the Mux Team on Vercel.

A member of the Team first needs to authorize it.

@spuppo-mux
Copy link
Copy Markdown
Contributor Author

Closes #219

@spuppo-mux spuppo-mux linked an issue Mar 23, 2026 that may be closed by this pull request
Comment thread packages/vimeo-video-element/vimeo-video-element.js
Comment thread packages/vimeo-video-element/vimeo-video-element.js Outdated
Comment thread packages/vimeo-video-element/vimeo-video-element.js
Comment thread packages/vimeo-video-element/vimeo-video-element.js Outdated
Comment thread packages/vimeo-video-element/vimeo-video-element.js
…d loadComplete renewal to after the src check
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6de507c. Configure here.

Comment thread packages/vimeo-video-element/vimeo-video-element.js
@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
media-elements-nextjs Ready Ready Preview, Comment May 6, 2026 8:09pm

Request Review

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.

<vimeo-video> config not being set

2 participants