Skip to content

Fix 'Cannot have two HTML5 backends at the same time' in DataGridView. #10485 - #10487

Closed
kundansable wants to merge 2 commits into
pgadmin-org:masterfrom
kundansable:fix-10485-dnd-two-backends
Closed

kundansable wants to merge 2 commits into
pgadmin-org:masterfrom
kundansable:fix-10485-dnd-two-backends

Conversation

@kundansable

@kundansable kundansable commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #10485

Cause

DataGridView used <DndProvider backend={HTML5Backend}>. With only a backend prop, react-dnd keeps its manager in a global singleton (window[Symbol.for('__REACT_DND_CONTEXT_INSTANCE__')]) guarded by a module-level refCount, and nulls it when the count reaches 0.

react-arborist (used by PgTreeView) bundles react-dnd 14. It stores its manager under the same global key but keeps a separate refCount. Dialogs mount in separate React roots, so when a grid and a tree were mounted together, unmounting whichever one created the manager nulled it while the other was still using its HTML5 backend on window. The next grid created a second backend, and setup() threw "Cannot have two HTML5 backends at the same time" from DataGridRow (react-dnd/react-dnd#3178).

Steps to reproduce (on master)

The trigger is a DataGridView with reorderable rows plus a react-arborist tree (PgTreeView) open at the same time, then closing one of them. It is deterministic, not timing-dependent. react-arborist bundles react-dnd 14, which stores its manager under the same global key as react-dnd 16 but keeps a separate reference count. Whichever one drops to zero nulls the shared manager while the other still has the HTML5 backend set up on window.

The columns grid only registers drag sources when rows can be reordered (canReorder, which is true for a new table). In the Preferences dialog, the left-hand category tree is a PgTreeView.

Order A: tree first

  1. Open File > Preferences and leave the Preferences tab open.
  2. In the Object Explorer, right-click Tables under a schema and choose Create > Table....
  3. Go to the Columns tab and click + to add a column. Leave this dialog open.
  4. Close the Preferences tab.
  5. Open a second Create > Table... dialog, go to Columns, and click +.
  6. The second dialog's body shows "Something went wrong." and the console logs Error: Cannot have two HTML5 backends at the same time., coming from <DataGridRow>.

Order B: grid first

  1. Open Create > Table..., go to Columns, and click + to add a column.
  2. Open File > Preferences and leave it open.
  3. Close the Create Table dialog.
  4. Open Create > Table... again, go to Columns, and click +. You get the same error.

Both orders are reproduced as automated tests in regression/javascript/SchemaView/DataGridViewDnd.spec.js (see the "DataGridView together with PgTreeView" tests). On master they fail with exactly this error. The UI steps above are derived from that reproduction and the code paths. They have not yet been run manually in a browser.

After this change, both orders render the grid normally.

Fix

  • New sources/dnd_manager.js builds a single app-wide manager with createDragDropManager(HTML5Backend) from dnd-core 16, which is now a direct dependency. It resolves to the version react-dnd 16 already uses, so the only lockfile change is the new specifier.
  • DataGridView passes it as <DndProvider manager={...}>, and PgTreeView passes it as react-arborist's dndManager. An explicit manager bypasses the global singleton and its ref counting, so the manager is never torn down and only one backend is ever set up on window.
  • Removed PgTreeView's dndRootElement. Its ref was never attached to an element, so it always resolved to window anyway.

Tests

regression/javascript/SchemaView/DataGridViewDnd.spec.js uses a reorderable collection so the HTML5 backend is actually set up. It covers:

  • the shared manager being a stable singleton that the grid uses, with nothing written to react-dnd's global key
  • the backend being set up and torn down on window
  • grids mounted and unmounted across multiple roots
  • grid + PgTreeView in separate roots, unmounted in either order, then a new grid mounted

On master, 3 of these tests fail with the exact error from the issue. All pass with this change. The full JS suite (976 tests) and the linter pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed drag-and-drop issues when grids and the tree view are mounted together or unmounted independently. Separately rendered grids can now be mounted and unmounted without triggering an error about multiple HTML5 backends.

…pgadmin-org#10485

Pass an explicit context to DndProvider so react-dnd does not null its
shared manager when one root's provider unmounts while another root's grid
still uses the HTML5 backend.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 1, 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: Repository: pgadmin-org/pgadmin4/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: df5817ad-df7c-4d36-bd5f-ab2be9c89303

📥 Commits

Reviewing files that changed from the base of the PR and between 528189f and ab3f9c8.

⛔ Files ignored due to path filters (1)
  • web/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (5)
  • web/package.json
  • web/pgadmin/static/js/PgTreeView/index.jsx
  • web/pgadmin/static/js/SchemaView/DataGridView/grid.jsx
  • web/pgadmin/static/js/dnd_manager.js
  • web/regression/javascript/SchemaView/DataGridViewDnd.spec.js

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


Walkthrough

DataGridView and PgTreeView now use a cached React DnD manager with an HTML5 backend. Regression tests cover shared manager identity and grid and tree behavior across separate React roots.

Changes

Shared drag-and-drop manager

Layer / File(s) Summary
Shared manager initialization
web/package.json, web/pgadmin/static/js/dnd_manager.js
The new getDndManager() function lazily creates and caches a manager configured with HTML5Backend. The package dependencies include dnd-core.
Grid and tree integration
web/pgadmin/static/js/SchemaView/DataGridView/grid.jsx, web/pgadmin/static/js/PgTreeView/index.jsx, web/regression/javascript/SchemaView/DataGridViewDnd.spec.js
Both components pass the shared manager to DndProvider. The tree removes its dndRootElement option and unused container ref. Regression tests cover manager identity, backend setup, and mounting and unmounting grids and trees across React roots.

Priority: ➖ Normal

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

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: dpage

Merge Risk: ⚪ Minimal · up to ab3f9

The shared manager addresses overlapping grid and tree lifecycles without changing the tree's effective backend scope. No actionable merge-blocking defect was established; merge after normal checks, including the added regression tests.

Security Architecture Review

Security architecture risk: 🔵 Low · up to ab3f9

The change shares drag-and-drop state within one browser page. No newly reachable privileged operation is established, but cleanup during interrupted drags and compatibility across dependency versions remain unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated shared-state scope is consumers of this module within one browser page, including separate React roots. The traced mutation sink is a grid's schema-state MOVE_ROW dispatch; these paths do not establish cross-user, cross-service or credential authority gained through the new helper.

Trust Boundaries and Controls

  • observed — PgTreeView retains disableDrag and disableDrop defaults. Caller props can override them, but that ordering predates this PR. Sharing a manager consequently does not itself enable a tree drag/drop operation on the default path.

Resilience and Maintainability Implications

  • inferred — Successful synchronous construction is published once, preventing separate callers of this module from independently creating managers. If construction throws before assignment, the cache remains unset. Subsequent backend failure, active-drag cancellation and stale item cleanup depend on library lifecycle behavior that is not proven by the inspected mount/remount assertions.

Hardening Proposals

  • proposed — As separate hardening of the existing reorder contract, attach collection-owner identity to row items and reject incompatible hover targets. Validate that interrupting a drag by unmounting either the source or final consumer cannot replay a stale reorder when another root remains or remounts.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 4 files. (1 skipped: 1 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses issue #10485. dnd_manager.js creates one shared dnd-core 16 manager. DataGridView and PgTreeView use that manager. PgTreeView also removes the unused dndRootElement path. …
Out of Scope Changes check ✅ Passed The changes are limited to the shared DnD manager, its use in DataGridView and PgTreeView, the dnd-core dependency, and regression tests. Each change supports the fix for issue #10485. No unrela…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: preventing the “Cannot have two HTML5 backends at the same time” error in DataGridView. It is specific and related to the pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 4 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

…iew. pgadmin-org#10485

Passing context={window} only stopped DataGridView's react-dnd 16 from
nulling the global manager. react-arborist bundles react-dnd 14, which
stores its manager under the same global key with its own reference
count, so unmounting a PgTreeView could still drop the manager while a
grid's HTML5 backend was set up on window.

Create a single manager with dnd-core 16 and pass it explicitly to both
DndProvider (manager) and react-arborist (dndManager), bypassing the
global singleton entirely. Also drop the dndRootElement prop, whose ref
was never attached and so always resolved to window.

Extend the tests to cover grids and trees mounted in separate roots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dpage pushed a commit that referenced this pull request Oct 1, 2026
…#10485 (#10487)

Share one explicit react-dnd manager between DataGridView and PgTreeView. react-arborist bundles react-dnd 14, which stores its manager under the same global key as react-dnd 16 but keeps a separate reference count, so unmounting one provider could drop the manager while its HTML5 backend was still attached to window.
@dpage

dpage commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Thanks! Rebased onto master (the only conflicts were the neighbouring dependency bumps in package.json/yarn.lock), squashed and merged as 467a200.

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.

DataGridView can crash dialogs with "Cannot have two HTML5 backends at the same time"

2 participants