emberjs/ember.js · error · Error

Attempted to cast to a browser node with a node that was not

Error message

Attempted to cast to a browser node with a node that was not created from this document

What it means

castToBrowser verifies that the node's ownerDocument matches the current global document. When a node was created by a different document (e.g. an iframe, jsdom instance, or detached document), the cast is rejected to prevent cross-document DOM mixing, which causes subtle browser bugs.

Source

Thrown at packages/@glimmer/debug-util/lib/simple-cast.ts:91

): Document | NodeForSugaryCheck<S> | null {
  if (!LOCAL_DEBUG) {
    return node as Document | NodeForSugaryCheck<S> | null;
  }

  if (node === null || node === undefined) {
    return null;
  }

  if (typeof document === 'undefined') {
    throw new Error('Attempted to cast to a browser node in a non-browser context');
  }

  if (isDocument(node)) {
    return node as Document;
  }

  if (node.ownerDocument !== document) {
    throw new Error(
      'Attempted to cast to a browser node with a node that was not created from this document'
    );
  }

  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
  return checkBrowserNode(node, sugaryCheck!);
}

function checkError(from: string, check: SugaryNodeCheck): Error {
  return new Error(`cannot cast a ${from} into ${String(check)}`);
}

export const ELEMENT_NODE = 1;
export const DOCUMENT_NODE = 9;

function isDocument(node: Node | SimpleNode | SimpleDocument): node is Document | SimpleDocument {
  return node.nodeType === DOCUMENT_NODE;
}

View on GitHub (pinned to 26f97246a8)

Solutions

  1. Ensure nodes are created via the same global document the app runs in
  2. Use `document.importNode` / `adoptNode` to move nodes between documents before casting
  3. In tests, use a single shared jsdom document
  4. Avoid caching DOM nodes across iframe reloads

Example fix

// before
let el = iframe.contentDocument.createElement('div');
castToBrowser(el, 'Element');
// after
let el = document.importNode(iframe.contentDocument.createElement('div'), true);
castToBrowser(el, 'Element');
Defensive patterns

Strategy: validation

Validate before calling

function isFromCurrentDocument(node) {
  return node != null && node.ownerDocument === document;
}

Type guard

function isLocalNode(node) { return node != null && typeof node === 'object' && node.ownerDocument === document; }

Try / catch

try {
  node = castToBrowser(value, 'Element');
} catch (e) {
  if (String(e.message).includes('not created from this document')) {
    node = document.importNode(value, true);
  } else { throw e; }
}

Prevention

When it happens

Trigger: Passing a node created in an iframe or another Document instance to castToBrowser; mixing jsdom documents across test files; moving nodes between windows.

Common situations: Testing with multiple jsdom instances; code that clones or adopts nodes across iframes; browser extension content scripts operating on elements from different documents.

Related errors


AI-assisted analysis of emberjs/ember.js@26f97246a8 (2026-09-01). Data as JSON: /api/errors/4729bc154f6f1d03. Report an issue: GitHub.