GraphiteEditor/Graphite · error

window should have a document

Error message

window should have a document

What it means

`render_image_data_to_canvases` blits rasterized images onto canvases registered in `window.imageCanvases`. It already handles a missing `window` gracefully, but then calls `window.document()` — a `Option<Document>` — with `.expect("window should have a document")`. In a real browser a window always has a document, so this panic indicates a stubbed `window` object (tests, SSR shims) or an exotic embedding where the global `window` exists without a `document` property.

Source

Thrown at frontend/wrapper/src/helpers.rs:135

pub(crate) fn async_wake_callback() -> Wake {
	use std::sync::Arc;
	Arc::new(|| {
		wasm_bindgen_futures::spawn_local(async {
			wrapper(|wrapper| wrapper.dispatch(FutureMessage::Wake));
		});
	})
}

/// Blits each rasterized image to a canvas registered on `window.imageCanvases`
pub(crate) fn render_image_data_to_canvases<'a>(image_data: impl IntoIterator<Item = &'a RasterizedImage>) {
	let window = match window() {
		Some(window) => window,
		None => {
			error!("Cannot render canvas: window object not found");
			return;
		}
	};
	let document = window.document().expect("window should have a document");
	let window_obj = Object::from(window);
	let image_canvases_key = JsValue::from_str("imageCanvases");

	let canvases_obj = match Reflect::get(&window_obj, &image_canvases_key) {
		Ok(obj) if !obj.is_undefined() && !obj.is_null() => obj,
		_ => {
			let new_obj = Object::new();
			if Reflect::set(&window_obj, &image_canvases_key, &new_obj).is_err() {
				error!("Failed to create and set imageCanvases object on window");
				return;
			}
			new_obj.into()
		}
	};
	let canvases_obj = Object::from(canvases_obj);

	for image in image_data {
		let (placeholder_id, width, height) = (image.id, image.width, image.height);

View on GitHub (pinned to c507b35645)

Solutions

  1. Treat it like the adjacent window check: `let Some(document) = window.document() else { error!(...); return; }`.
  2. In tests, use a complete DOM (jsdom with default url, or a real browser) instead of hand-written `window` doubles.
  3. Avoid loading/initializing the editor before the DOM exists (mount-time dynamic import).
  4. Verify in the host page that `window.document` is present before creating the wrapper.

Example fix

// before
let document = window.document().expect("window should have a document");

// after
let Some(document) = window.document() else {
  error!("window has no document; cannot blit image canvases");
  return;
};
Defensive patterns

Strategy: validation

Validate before calling

// TS: probe the DOM before enabling image-canvas previews
if (typeof window === 'undefined' || !window.document) {
  console.warn('No window.document; image canvas blitting disabled');
}

Type guard

function hasDocument(): boolean {
  return typeof window !== 'undefined' && Boolean(window.document);
}

Prevention

When it happens

Trigger: A `FrontendMessage::UpdateImageData` being emitted while the wrapper runs under a partial DOM mock whose `window` lacks `document` (jsdom configured without a document, custom test doubles, or a compromised/polyfilled global scope).

Common situations: Unit tests with hand-rolled `window` stubs; SSR harnesses that define `window` to guard imports but never attach a document; scripts that delete or replace `window.document`.

Related errors


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/50a6bea92cf26d82. Report an issue: GitHub.