GraphiteEditor/Graphite · critical

Failed to serialize FrontendMessage

Error message

Failed to serialize FrontendMessage

What it means

Every `FrontendMessage` the Rust editor emits is converted to a JS value with `serde_wasm_bindgen` (with `serialize_large_number_types_as_bigints(true)`, so u64/i64 become JS `BigInt`) and handed to the JS callback registered at wrapper creation. The `.expect` fires when serialization returns `Err`, which happens for values that have no JS representation — classically non-string map keys (e.g. a `HashMap` keyed by a struct or tuple), unsupported number types, or a message variant whose payload type gained a field serde cannot map.

Source

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

	}

	#[cfg(feature = "editor")]
	pub(crate) fn send_frontend_message_to_js(&self, message: FrontendMessage) {
		if let FrontendMessage::UpdateImageData { ref image_data } = message {
			let new_hash = calculate_hash(image_data);
			let prev_hash = IMAGE_DATA_HASH.load(Ordering::Relaxed);

			if new_hash != prev_hash {
				render_image_data_to_canvases(image_data.iter());
				IMAGE_DATA_HASH.store(new_hash, Ordering::Relaxed);
			}
			return;
		}

		let message_type = message.to_discriminant().local_name();

		let serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);
		let message_data = message.serialize(&serializer).expect("Failed to serialize FrontendMessage");

		let js_return_value = self.frontend_message_handler_callback.call2(&JsValue::null(), &JsValue::from(message_type), &message_data);

		if let Err(error) = js_return_value {
			error!("While handling FrontendMessage {:?}, JavaScript threw an error:\n{:?}", message.to_discriminant().local_name(), error,)
		}
	}

	pub(crate) fn forward_serialized_frontend_message_to_js(&self, name: &str, data: crate::wasm_value::WasmValue) {
		let js_return_value = self.frontend_message_handler_callback.call2(&JsValue::null(), &JsValue::from(name), &data.into());

		if let Err(error) = js_return_value {
			error!("While handling FrontendMessage {name:?}, JavaScript threw an error:\n{error:?}")
		}
	}

	#[cfg(all(feature = "native", target_family = "wasm"))]
	pub(crate) fn send(&self, command: EditorCommand) {

View on GitHub (pinned to c507b35645)

Solutions

  1. Find the offending variant from the panic backtrace or by logging `message.to_discriminant().local_name()` just before serializing.
  2. Change message payloads to use string keys (`HashMap<String, _>` / `BTreeMap<String, _>`) or serialize as a Vec of pairs.
  3. Add a CI/test that serializes a sample of every `FrontendMessage` variant through the same `serde_wasm_bindgen::Serializer` configuration.
  4. Replace the `.expect` with a match that logs the discriminant and skips the message so one bad variant doesn't kill the editor.

Example fix

// before
let serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);
let message_data = message.serialize(&serializer).expect("Failed to serialize FrontendMessage");

// after
let serializer = serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true);
let Ok(message_data) = message.serialize(&serializer) else {
  error!("Failed to serialize FrontendMessage {:?}", message.to_discriminant().local_name());
  return;
};
Defensive patterns

Strategy: fallback

Validate before calling

// Rust: CI guard proving every FrontendMessage serializes like the wrapper does
fn serializer() -> serde_wasm_bindgen::Serializer {
  serde_wasm_bindgen::Serializer::new().serialize_large_number_types_as_bigints(true)
}
#[test]
fn frontend_messages_serialize() {
  for message in sample_of_every_frontend_message() {
    assert!(message.serialize(&serializer()).is_ok(),
      "{:?} is not representable as a JS value", message.to_discriminant());
  }
}

Try / catch

JS cannot catch the wasm panic; after suspect operations check `await editor.hasCrashed()`. In the wrapper itself, match on serialize() and log-and-skip the message so one bad variant degrades instead of killing all frontend updates.

Prevention

When it happens

Trigger: Adding a `FrontendMessage` variant containing a map with non-string keys (struct/enum/tuple keys), a `usize`/`u128` field on a serializer path not covered by the bigints setting, or any `Serialize` impl that emits an unsupported construct; the first time that message is emitted after a user action, the wrapper panics instead of delivering data.

Common situations: Contributors adding new frontend message payloads during feature work; refactoring a keyed collection into a message (e.g. `HashMap<NodeId, …>` without converting keys to strings first); upgrading serde/serde_wasm_bindgen versions that change supported representations.

Related errors


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