Skip to content

fix(custom-element): skip mounting an async custom element removed while its component loads (fix #15775) - #15777

Open
maciejbak89 wants to merge 1 commit into
vuejs:mainfrom
maciejbak89:fix/custom-element-async-mount-after-removal
Open

maciejbak89 wants to merge 1 commit into
vuejs:mainfrom
maciejbak89:fix/custom-element-async-mount-after-removal

Conversation

@maciejbak89

@maciejbak89 maciejbak89 commented Oct 5, 2026 •

Copy link
Copy Markdown

An async custom element removed from the document while its component is loading is still mounted when the component resolves. If its Vue custom element parent was removed with it, the mount throws on the parent's unmounted instance (reading 'provides'), and a custom element nested inside it stays blank after the tree is added back. Reproduction in #15775.

This checks this.isConnected before the initial mount in _resolveDef, as #15154 does for a child waiting on its parent. The definition still resolves and is cached, and connectedCallback mounts it when the element is connected again.

Tests: a parent / async child / grandchild case (fails on main: unhandled error and blank grandchild) and an async element with no Vue parent that must not run mounted while detached. minor has the same unguarded mount (this._mountComponent(def) in _resolveDef).

Ran the unit and unit-jsdom projects (3,747 passed, 6 skipped), plus prettier, eslint and the pre-commit tsc --noEmit; not the e2e or dts tests.

Summary by CodeRabbit

  • Bug Fixes
    • Resolved an issue where asynchronously defined custom elements could mount while detached. They now mount when connected, with nested content and parent-provided values available.

@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b3752926-5a77-4bfe-b6f5-d3f685fb3aa0
📥 Commits

Reviewing files that changed from the base of the PR and between 4ab865a and 86cea72.

📒 Files selected for processing (2)
  • packages/runtime-dom/__tests__/customElement.spec.ts
  • packages/runtime-dom/src/apiCustomElement.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Async custom elements now skip mounting when their definitions resolve while they are disconnected. Tests check that they mount after reconnection.

Changes

Async Custom Element Reconnection

Layer / File(s) Summary
Connected mounting and regression coverage
packages/runtime-dom/src/apiCustomElement.ts, packages/runtime-dom/__tests__/customElement.spec.ts
_resolveDef mounts an element only when it is connected. Tests cover a detached child and a standalone element mounting after reconnection. The child test also checks nested rendering and parent-provided injection.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: edison1105

Fixed issue severity: <fixed_issue_severity>Low</fixed_issue_severity>

Merge Risk: ⚪ Minimal · up to 86cea

Detached async custom elements should wait to mount until reconnected. No actionable merge-blocking risk remains after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 86cea

The change prevents detached elements from mounting without adding public capabilities or authority. Risk is low, with less certainty around recovery when an element reconnects under different parent ownership.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated exposure is consumer DOM trees containing async Vue custom elements and their parent-provided context. The inspected change restricts execution while detached rather than introducing a new callable entrypoint or additional authority.

Trust Boundaries and Controls

  • observed — DOM ancestry selects the nearest Vue custom-element parent, whose provides object is inherited by the child application context. Reconnection replaces the stored parent when another Vue parent is found. This ownership and context-transfer mechanism is unchanged between base and head.

Resilience and Maintainability Implications

  • inferred — Reconnection without a Vue parent does not clear the stored prior parent. This can retain old injection context or fail if that parent's instance has been unmounted. Parent retention and partial-mount cleanup limitations predate the PR; no increased security exposure was established. The new tests cover original-tree recovery, not changed-parent recovery.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: skip mounting an async custom element that is removed while its component loads. The issue reference adds context without making the title unclear.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
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.

1 participant