From 4f21693772e4d59333e7cc08d70995033fb174de Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 25 Jul 2025 18:13:51 -0700 Subject: [PATCH] Create function for CEF to call --- frontend/wasm/src/native.rs | 135 ++++++++++++++++++++++++++++++++++-- package.json | 2 +- 2 files changed, 130 insertions(+), 7 deletions(-) diff --git a/frontend/wasm/src/native.rs b/frontend/wasm/src/native.rs index 53ed32a5e..90d02bfed 100644 --- a/frontend/wasm/src/native.rs +++ b/frontend/wasm/src/native.rs @@ -1,21 +1,74 @@ -use std::cell::RefCell; -use std::time::Duration; - use crate::Message; use editor::messages::prelude::*; -use wasm_bindgen::closure::Closure; +use graphene_std::Color; +use graphene_std::raster::Image; +use js_sys::{Object, Reflect}; +use serde::ser::Serialize; +use std::sync::atomic::{AtomicU64, Ordering}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsCast, JsValue}; +use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData, window}; + +// TODO: Remove +static IMAGE_DATA_HASH: AtomicU64 = AtomicU64::new(0); +fn calculate_hash(t: &T) -> u64 { + use std::collections::hash_map::DefaultHasher; + use std::hash::Hasher; + let mut hasher = DefaultHasher::new(); + t.hash(&mut hasher); + hasher.finish() +} #[wasm_bindgen] #[derive(Clone)] -pub struct EditorHandle; +pub struct EditorHandle { + /// TODO: Remove + /// We current do frontend message in native -> serde serialize -> json string -> serde deserialize -> frontend message in wasm -> JSValue -> browser + /// We should do native -> V8Value -> browser. + frontend_message_handler_callback: js_sys::Function, +} #[wasm_bindgen] impl EditorHandle { #[wasm_bindgen(constructor)] pub fn new(frontend_message_handler_callback: js_sys::Function) -> Self { - EditorHandle + EditorHandle { frontend_message_handler_callback } + } + + /// TODO: Remove + #[wasm_bindgen(js_name = sendMessageToFrontendFromCEF)] + pub fn send_message_to_frontend_from_cef(&self, message: String) { + let Ok(mut message) = serde_json::from_str::(&message) else { return }; + + 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.as_slice()); + IMAGE_DATA_HASH.store(new_hash, Ordering::Relaxed); + } + return; + } + + if let FrontendMessage::UpdateDocumentLayerStructure { data_buffer } = message { + message = FrontendMessage::UpdateDocumentLayerStructureJs { data_buffer: data_buffer.into() }; + } + + 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: {:?}", + message.to_discriminant().local_name(), + error, + ) + } } } @@ -43,3 +96,73 @@ pub fn send_message_to_cef>(message: T) { // Call it with argument func.call1(&JsValue::NULL, &JsValue::from_str(&serialized_message)).expect("Function call failed"); } + +// TODO: Remove +fn render_image_data_to_canvases(image_data: &[(u64, Image)]) { + 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 (placeholder_id, image) in image_data.iter() { + let canvas_name = placeholder_id.to_string(); + let js_key = JsValue::from_str(&canvas_name); + + if Reflect::has(&canvases_obj, &js_key).unwrap_or(false) || image.width == 0 || image.height == 0 { + continue; + } + + let canvas: HtmlCanvasElement = document + .create_element("canvas") + .expect("Failed to create canvas element") + .dyn_into::() + .expect("Failed to cast element to HtmlCanvasElement"); + + canvas.set_width(image.width); + canvas.set_height(image.height); + + let context: CanvasRenderingContext2d = canvas + .get_context("2d") + .expect("Failed to get 2d context") + .expect("2d context was not found") + .dyn_into::() + .expect("Failed to cast context to CanvasRenderingContext2d"); + let u8_data: Vec = image.data.iter().flat_map(|color| color.to_rgba8_srgb()).collect(); + let clamped_u8_data = wasm_bindgen::Clamped(&u8_data[..]); + match ImageData::new_with_u8_clamped_array_and_sh(clamped_u8_data, image.width, image.height) { + Ok(image_data_obj) => { + if context.put_image_data(&image_data_obj, 0., 0.).is_err() { + error!("Failed to put image data on canvas for id: {placeholder_id}"); + } + } + Err(e) => { + error!("Failed to create ImageData for id: {placeholder_id}: {e:?}"); + } + } + + let js_value = JsValue::from(canvas); + + if Reflect::set(&canvases_obj, &js_key, &js_value).is_err() { + error!("Failed to set canvas '{canvas_name}' on imageCanvases object"); + } + } +} diff --git a/package.json b/package.json index 7197418fb..9bdfdae2e 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "scripts": { "---------- DEV SERVER ----------": "", "start": "cd frontend && npm start", - "start-desktop": "cd frontend && npm run build-dev && cargo run -p graphite-desktop", + "start-desktop": "cd frontend && npm run build-native && cargo run -p graphite-desktop", "profiling": "cd frontend && npm run profiling", "production": "cd frontend && npm run production", "---------- BUILDS ----------": "",