diff --git a/frontend/playwright/data/text-editor/update-file-11552.json b/frontend/playwright/data/text-editor/update-file-11552.json index e556b830cf..0967ef424b 100644 --- a/frontend/playwright/data/text-editor/update-file-11552.json +++ b/frontend/playwright/data/text-editor/update-file-11552.json @@ -1 +1 @@ -w +{} diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs index 51058b5fe4..6466e414da 100644 --- a/frontend/src/app/main/errors.cljs +++ b/frontend/src/app/main/errors.cljs @@ -116,6 +116,17 @@ (ex/print-throwable cause :prefix "Unexpected Error") (show-not-blocking-error cause)))) +(defmethod ptk/handle-error :wasm-non-blocking + [error] + (when-let [cause (::instance error)] + (show-not-blocking-error cause))) + +(defmethod ptk/handle-error :wasm-critical + [error] + (when-let [cause (::instance error)] + (ex/print-throwable cause :prefix "WASM critical error")) + (st/emit! (rt/assign-exception error))) + ;; We receive a explicit authentication error; If the uri is for ;; workspace, dashboard, viewer or settings, then assign the exception ;; for show the error page. Otherwise this explicitly clears all @@ -327,20 +338,24 @@ (str/starts-with? message "invalid props on component") (str/starts-with? message "Unexpected token ")))) + (handle-uncaught [cause] + (when cause + (set! last-exception cause) + (let [data (ex-data cause) + type (get data :type)] + (if (#{:wasm-critical :wasm-non-blocking} type) + (on-error cause) + (when-not (is-ignorable-exception? cause) + (ex/print-throwable cause :prefix "Uncaught Exception") + (ts/schedule #(show-not-blocking-error cause))))))) + (on-unhandled-error [event] (.preventDefault ^js event) - (when-let [cause (unchecked-get event "error")] - (set! last-exception cause) - (when-not (is-ignorable-exception? cause) - (ex/print-throwable cause :prefix "Uncaught Exception") - (ts/schedule #(show-not-blocking-error cause))))) + (handle-uncaught (unchecked-get event "error"))) (on-unhandled-rejection [event] (.preventDefault ^js event) - (when-let [cause (unchecked-get event "reason")] - (set! last-exception cause) - (ex/print-throwable cause :prefix "Uncaught Rejection") - (ts/schedule #(show-not-blocking-error cause))))] + (handle-uncaught (unchecked-get event "reason")))] (.addEventListener g/window "error" on-unhandled-error) (.addEventListener g/window "unhandledrejection" on-unhandled-rejection) diff --git a/frontend/src/app/render_wasm/helpers.cljc b/frontend/src/app/render_wasm/helpers.cljc index 5cb9b5f5ac..5b973cd837 100644 --- a/frontend/src/app/render_wasm/helpers.cljc +++ b/frontend/src/app/render_wasm/helpers.cljc @@ -7,11 +7,30 @@ (ns app.render-wasm.helpers #?(:cljs (:require-macros [app.render-wasm.helpers]))) +(def ^:export error-code + "WASM error code constants (must match render-wasm/src/error.rs and mem.rs)." + {0x01 :wasm-non-blocking 0x02 :wasm-critical}) + (defmacro call - "A helper for easy call wasm defined function in a module." + "A helper for calling a wasm function. + Catches any exception thrown by the WASM function, reads the error code from + WASM when available, and routes it based on the error type: + - :wasm-non-blocking: call app.main.errors/on-error (eventually, shows a toast and logs the error) + - :wasm-critical or unknown: throws an exception to be handled by the global error handler (eventually, shows the internal error page)" [module name & params] - (let [fn-sym (with-meta (gensym "fn-") {:tag 'function})] + (let [fn-sym (with-meta (gensym "fn-") {:tag 'function}) + e-sym (gensym "e") + code-sym (gensym "code")] `(let [~fn-sym (cljs.core/unchecked-get ~module ~name)] - ;; DEBUG - ;; (println "##" ~name) - (~fn-sym ~@params)))) + (try + (~fn-sym ~@params) + (catch :default ~e-sym + (let [read-code# (cljs.core/unchecked-get ~module "_read_error_code") + ~code-sym (when read-code# (read-code#)) + type# (or (get app.render-wasm.helpers/error-code ~code-sym) :wasm-critical) + ex# (ex-info (str "WASM error (type: " type# ")") + {:fn ~name :type type# :message (.-message ~e-sym) :error-code ~code-sym} + ~e-sym)] + (if (= type# :wasm-non-blocking) + (@~'app.main.store/on-error ex#) + (throw ex#)))))))) diff --git a/render-wasm/Cargo.lock b/render-wasm/Cargo.lock index e5c289d99e..5d749143fd 100644 --- a/render-wasm/Cargo.lock +++ b/render-wasm/Cargo.lock @@ -297,6 +297,8 @@ name = "macros" version = "0.1.0" dependencies = [ "heck", + "proc-macro2", + "quote", "syn", ] @@ -426,6 +428,7 @@ dependencies = [ "indexmap", "macros", "skia-safe", + "thiserror", "uuid", ] @@ -579,6 +582,26 @@ dependencies = [ "xattr", ] +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "1.0.3+spec-1.1.0" diff --git a/render-wasm/Cargo.toml b/render-wasm/Cargo.toml index ca37fe4104..a26e798e13 100644 --- a/render-wasm/Cargo.toml +++ b/render-wasm/Cargo.toml @@ -32,6 +32,7 @@ skia-safe = { version = "0.93.1", default-features = false, features = [ "binary-cache", "webp", ] } +thiserror = "2.0.18" uuid = { version = "1.11.0", features = ["v4", "js"] } [profile.release] diff --git a/render-wasm/macros/Cargo.lock b/render-wasm/macros/Cargo.lock index 8b4fd748ae..8612f3a90b 100644 --- a/render-wasm/macros/Cargo.lock +++ b/render-wasm/macros/Cargo.lock @@ -13,6 +13,8 @@ name = "macros" version = "0.1.0" dependencies = [ "heck", + "proc-macro2", + "quote", "syn", ] diff --git a/render-wasm/macros/Cargo.toml b/render-wasm/macros/Cargo.toml index 6c2abd7509..f3738381b8 100644 --- a/render-wasm/macros/Cargo.toml +++ b/render-wasm/macros/Cargo.toml @@ -1,11 +1,13 @@ [package] name = "macros" version = "0.1.0" -edition = "2024" +edition = "2021" [lib] proc-macro = true [dependencies] heck = "0.5.0" +proc-macro2 = "1.0" +quote = "1.0" syn = "2.0.106" diff --git a/render-wasm/macros/src/lib.rs b/render-wasm/macros/src/lib.rs index a0eec23ca6..2d3536d3d1 100644 --- a/render-wasm/macros/src/lib.rs +++ b/render-wasm/macros/src/lib.rs @@ -6,9 +6,109 @@ use std::sync; use heck::{ToKebabCase, ToPascalCase}; use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Block, GenericArgument, ItemFn, ReturnType, Type}; type Result = std::result::Result; +/// Attribute macro for WASM-exported functions. The function **must** return +/// `std::result::Result` where T is a C ABI type and E implements +/// `std::error::Error` and `Into`. The macro: +/// - Clears the error code at entry. +/// - Runs the body in `std::panic::catch_unwind`. +/// - Unwraps the Result: `Ok(x)` → return x; `Err(e)` → set error code in memory and panic +/// (so ClojureScript can catch the exception and read the code via `read_error_code`). +/// - On panic from the body: sets critical error code (0x02) and resumes unwind. +#[proc_macro_attribute] +pub fn wasm_error(_attr: TokenStream, item: TokenStream) -> TokenStream { + let mut input = parse_macro_input!(item as ItemFn); + let body = (*input.block).clone(); + + let (attrs, boxed_ty) = match &input.sig.output { + ReturnType::Type(attrs, boxed_ty) => (attrs, boxed_ty), + ReturnType::Default => { + return quote! { + compile_error!( + "#[wasm_error] requires the function to return std::result::Result where E: std::error::Error + Into" + ); + } + .into(); + } + }; + + let (inner_ty, error_ty) = match crate_error_result_inner_type(boxed_ty) { + Some(t) => (t, quote!(crate::error::Error)), + None => { + return quote! { + compile_error!( + "#[wasm_error] requires the function to return crate::error::Result. T must be a C ABI type (u32, u8, bool, (), etc.)" + ); + } + .into(); + } + }; + + let block: Block = syn::parse2(quote! { + { + crate::mem::clear_error_code(); + let __wasm_err_result = std::panic::catch_unwind(|| -> std::result::Result<#inner_ty, #error_ty> { + #body + }); + match __wasm_err_result { + Ok(__inner) => match __inner { + Ok(__val) => __val, + Err(__e) => { + let _: &dyn std::error::Error = &__e; + let __msg = __e.to_string(); + crate::mem::set_error_code(__e.into()); + panic!("WASM error: {}",__msg); + } + }, + Err(__payload) => { + crate::mem::set_error_code(0x02); // critical, same as Error::Critical + std::panic::resume_unwind(__payload); + } + } + } + }) + .expect("block parse"); + + input.sig.output = ReturnType::Type(attrs.clone(), Box::new(inner_ty.clone())); + input.block = Box::new(block); + quote! { #input }.into() +} + +/// If the type is crate::error::Result or a single-segment Result (e.g. with +/// `use crate::error::Result`), returns Some(T). Otherwise None. +fn crate_error_result_inner_type(ty: &Type) -> Option<&Type> { + let path = match ty { + Type::Path(tp) => &tp.path, + _ => return None, + }; + let segs: Vec<_> = path.segments.iter().collect(); + let last = path.segments.last()?; + if last.ident != "Result" { + return None; + } + let args = match &last.arguments { + syn::PathArguments::AngleBracketed(a) => &a.args, + _ => return None, + }; + if args.len() != 1 { + return None; + } + // Accept crate::error::Result or bare Result (from use) + let ok = segs.len() == 1 + || (segs.len() == 3 && segs[0].ident == "crate" && segs[1].ident == "error"); + if !ok { + return None; + } + match &args[0] { + GenericArgument::Type(t) => Some(t), + _ => None, + } +} + #[proc_macro_derive(ToJs)] pub fn derive_to_cljs(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); diff --git a/render-wasm/src/error.rs b/render-wasm/src/error.rs new file mode 100644 index 0000000000..413a81afff --- /dev/null +++ b/render-wasm/src/error.rs @@ -0,0 +1,25 @@ +use thiserror::Error; + +pub const RECOVERABLE_ERROR: u8 = 0x01; +pub const CRITICAL_ERROR: u8 = 0x02; + +// This is not really dead code, #[wasm_error] macro replaces this by something else. +#[allow(dead_code)] +pub type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum Error { + #[error("[Recoverable] {0}")] + RecoverableError(String), + #[error("[Critical] {0}")] + CriticalError(String), +} + +impl From for u8 { + fn from(error: Error) -> Self { + match error { + Error::RecoverableError(_) => RECOVERABLE_ERROR, + Error::CriticalError(_) => CRITICAL_ERROR, + } + } +} diff --git a/render-wasm/src/main.rs b/render-wasm/src/main.rs index c435e03ded..6e519cc249 100644 --- a/render-wasm/src/main.rs +++ b/render-wasm/src/main.rs @@ -1,5 +1,6 @@ #[cfg(target_arch = "wasm32")] mod emscripten; +mod error; mod math; mod mem; mod options; @@ -14,12 +15,16 @@ mod view; mod wapi; mod wasm; +use std::collections::HashMap; + +#[allow(unused_imports)] +use crate::error::{Error, Result}; +use macros::wasm_error; use math::{Bounds, Matrix}; use mem::SerializableResult; use shapes::{StructureEntry, StructureEntryType, TransformEntry}; use skia_safe as skia; use state::State; -use std::collections::HashMap; use utils::uuid_from_u32_quartet; use uuid::Uuid; @@ -95,22 +100,27 @@ macro_rules! with_state_mut_current_shape { } #[no_mangle] -pub extern "C" fn init(width: i32, height: i32) { +#[wasm_error] +pub extern "C" fn init(width: i32, height: i32) -> Result<()> { let state_box = Box::new(State::new(width, height)); unsafe { STATE = Some(state_box); } + Ok(()) } #[no_mangle] -pub extern "C" fn set_browser(browser: u8) { +#[wasm_error] +pub extern "C" fn set_browser(browser: u8) -> Result<()> { with_state_mut!(state, { state.set_browser(browser); }); + Ok(()) } #[no_mangle] -pub extern "C" fn clean_up() { +#[wasm_error] +pub extern "C" fn clean_up() -> Result<()> { with_state_mut!(state, { // Cancel the current animation frame if it exists so // it won't try to render without context @@ -118,49 +128,60 @@ pub extern "C" fn clean_up() { render_state.cancel_animation_frame(); }); unsafe { STATE = None } - mem::free_bytes(); + mem::free_bytes()?; + Ok(()) } #[no_mangle] -pub extern "C" fn set_render_options(debug: u32, dpr: f32) { +#[wasm_error] +pub extern "C" fn set_render_options(debug: u32, dpr: f32) -> Result<()> { with_state_mut!(state, { let render_state = state.render_state_mut(); render_state.set_debug_flags(debug); render_state.set_dpr(dpr); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_canvas_background(raw_color: u32) { +#[wasm_error] +pub extern "C" fn set_canvas_background(raw_color: u32) -> Result<()> { with_state_mut!(state, { let color = skia::Color::new(raw_color); state.set_background_color(color); state.rebuild_tiles_shallow(); }); + + Ok(()) } #[no_mangle] -pub extern "C" fn render(_: i32) { +#[wasm_error] +pub extern "C" fn render(_: i32) -> Result<()> { with_state_mut!(state, { state.rebuild_touched_tiles(); state .start_render_loop(performance::get_time()) .expect("Error rendering"); }); + Ok(()) } #[no_mangle] -pub extern "C" fn render_sync() { +#[wasm_error] +pub extern "C" fn render_sync() -> Result<()> { with_state_mut!(state, { state.rebuild_tiles(); state .render_sync(performance::get_time()) .expect("Error rendering"); }); + Ok(()) } #[no_mangle] -pub extern "C" fn render_sync_shape(a: u32, b: u32, c: u32, d: u32) { +#[wasm_error] +pub extern "C" fn render_sync_shape(a: u32, b: u32, c: u32, d: u32) -> Result<()> { with_state_mut!(state, { let id = uuid_from_u32_quartet(a, b, c, d); state.use_shape(id); @@ -179,34 +200,42 @@ pub extern "C" fn render_sync_shape(a: u32, b: u32, c: u32, d: u32) { state.rebuild_tiles_from(Some(&id)); state .render_sync_shape(&id, performance::get_time()) - .expect("Error rendering"); + .map_err(|e| Error::RecoverableError(e.to_string()))?; }); + Ok(()) } #[no_mangle] -pub extern "C" fn render_from_cache(_: i32) { +#[wasm_error] +pub extern "C" fn render_from_cache(_: i32) -> Result<()> { with_state_mut!(state, { state.render_state.cancel_animation_frame(); state.render_from_cache(); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_preview_mode(enabled: bool) { +#[wasm_error] +pub extern "C" fn set_preview_mode(enabled: bool) -> Result<()> { with_state_mut!(state, { state.render_state.set_preview_mode(enabled); }); + Ok(()) } #[no_mangle] -pub extern "C" fn render_preview() { +#[wasm_error] +pub extern "C" fn render_preview() -> Result<()> { with_state_mut!(state, { state.render_preview(performance::get_time()); }); + Ok(()) } #[no_mangle] -pub extern "C" fn process_animation_frame(timestamp: i32) { +#[wasm_error] +pub extern "C" fn process_animation_frame(timestamp: i32) -> Result<()> { let result = std::panic::catch_unwind(|| { with_state_mut!(state, { state @@ -225,37 +254,45 @@ pub extern "C" fn process_animation_frame(timestamp: i32) { std::panic::resume_unwind(err); } } + Ok(()) } #[no_mangle] -pub extern "C" fn reset_canvas() { +#[wasm_error] +pub extern "C" fn reset_canvas() -> Result<()> { with_state_mut!(state, { state.render_state_mut().reset_canvas(); }); + Ok(()) } #[no_mangle] -pub extern "C" fn resize_viewbox(width: i32, height: i32) { +#[wasm_error] +pub extern "C" fn resize_viewbox(width: i32, height: i32) -> Result<()> { with_state_mut!(state, { state.resize(width, height); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) { +#[wasm_error] +pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) -> Result<()> { with_state_mut!(state, { performance::begin_measure!("set_view"); let render_state = state.render_state_mut(); render_state.set_view(zoom, x, y); performance::end_measure!("set_view"); }); + Ok(()) } #[cfg(feature = "profile-macros")] static mut VIEW_INTERACTION_START: i32 = 0; #[no_mangle] -pub extern "C" fn set_view_start() { +#[wasm_error] +pub extern "C" fn set_view_start() -> Result<()> { with_state_mut!(state, { #[cfg(feature = "profile-macros")] unsafe { @@ -265,10 +302,12 @@ pub extern "C" fn set_view_start() { state.render_state.options.set_fast_mode(true); performance::end_measure!("set_view_start"); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_view_end() { +#[wasm_error] +pub extern "C" fn set_view_end() -> Result<()> { with_state_mut!(state, { let _end_start = performance::begin_timed_log!("set_view_end"); performance::begin_measure!("set_view_end"); @@ -304,17 +343,21 @@ pub extern "C" fn set_view_end() { performance::console_log!("[PERF] view_interaction: {}ms", total_time); } }); + Ok(()) } #[no_mangle] -pub extern "C" fn clear_focus_mode() { +#[wasm_error] +pub extern "C" fn clear_focus_mode() -> Result<()> { with_state_mut!(state, { state.clear_focus_mode(); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_focus_mode() { +#[wasm_error] +pub extern "C" fn set_focus_mode() -> Result<()> { let bytes = mem::bytes(); let entries: Vec = bytes @@ -325,83 +368,111 @@ pub extern "C" fn set_focus_mode() { with_state_mut!(state, { state.set_focus_mode(entries); }); + Ok(()) } #[no_mangle] -pub extern "C" fn init_shapes_pool(capacity: usize) { +#[wasm_error] +pub extern "C" fn init_shapes_pool(capacity: usize) -> Result<()> { with_state_mut!(state, { state.init_shapes_pool(capacity); }); + Ok(()) } #[no_mangle] -pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) { +#[wasm_error] +pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) -> Result<()> { with_state_mut!(state, { let id = uuid_from_u32_quartet(a, b, c, d); state.use_shape(id); }); + Ok(()) } #[no_mangle] -pub extern "C" fn touch_shape(a: u32, b: u32, c: u32, d: u32) { +#[wasm_error] +pub extern "C" fn touch_shape(a: u32, b: u32, c: u32, d: u32) -> Result<()> { with_state_mut!(state, { let shape_id = uuid_from_u32_quartet(a, b, c, d); state.touch_shape(shape_id); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_parent(a: u32, b: u32, c: u32, d: u32) { +#[wasm_error] +pub extern "C" fn set_parent(a: u32, b: u32, c: u32, d: u32) -> Result<()> { with_state_mut!(state, { let id = uuid_from_u32_quartet(a, b, c, d); state.set_parent_for_current_shape(id); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_shape_masked_group(masked: bool) { +#[wasm_error] +pub extern "C" fn set_shape_masked_group(masked: bool) -> Result<()> { with_current_shape_mut!(state, |shape: &mut Shape| { shape.set_masked(masked); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) { +#[wasm_error] +pub extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) -> Result<()> { with_current_shape_mut!(state, |shape: &mut Shape| { shape.set_selrect(left, top, right, bottom); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_shape_clip_content(clip_content: bool) { +#[wasm_error] +pub extern "C" fn set_shape_clip_content(clip_content: bool) -> Result<()> { with_current_shape_mut!(state, |shape: &mut Shape| { shape.set_clip(clip_content); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_shape_rotation(rotation: f32) { +#[wasm_error] +pub extern "C" fn set_shape_rotation(rotation: f32) -> Result<()> { with_current_shape_mut!(state, |shape: &mut Shape| { shape.set_rotation(rotation); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_shape_transform(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) { +#[wasm_error] +pub extern "C" fn set_shape_transform( + a: f32, + b: f32, + c: f32, + d: f32, + e: f32, + f: f32, +) -> Result<()> { with_current_shape_mut!(state, |shape: &mut Shape| { shape.set_transform(a, b, c, d, e, f); }); + Ok(()) } #[no_mangle] -pub extern "C" fn add_shape_child(a: u32, b: u32, c: u32, d: u32) { +#[wasm_error] +pub extern "C" fn add_shape_child(a: u32, b: u32, c: u32, d: u32) -> Result<()> { with_current_shape_mut!(state, |shape: &mut Shape| { let id = uuid_from_u32_quartet(a, b, c, d); shape.add_child(id); }); + Ok(()) } -fn set_children_set(entries: Vec) { +fn set_children_set(entries: Vec) -> Result<()> { let mut deleted = Vec::new(); let mut parent_id = None; @@ -420,7 +491,9 @@ fn set_children_set(entries: Vec) { with_state_mut!(state, { let Some(parent_id) = parent_id else { - return; + return Err(Error::RecoverableError( + "set_children_set: Parent ID not found".to_string(), + )); }; for id in deleted { @@ -428,21 +501,27 @@ fn set_children_set(entries: Vec) { state.touch_shape(id); } }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_children_0() { +#[wasm_error] +pub extern "C" fn set_children_0() -> Result<()> { let entries = vec![]; - set_children_set(entries); + set_children_set(entries)?; + Ok(()) } #[no_mangle] -pub extern "C" fn set_children_1(a1: u32, b1: u32, c1: u32, d1: u32) { +#[wasm_error] +pub extern "C" fn set_children_1(a1: u32, b1: u32, c1: u32, d1: u32) -> Result<()> { let entries = vec![uuid_from_u32_quartet(a1, b1, c1, d1)]; - set_children_set(entries); + set_children_set(entries)?; + Ok(()) } #[no_mangle] +#[wasm_error] pub extern "C" fn set_children_2( a1: u32, b1: u32, @@ -452,15 +531,17 @@ pub extern "C" fn set_children_2( b2: u32, c2: u32, d2: u32, -) { +) -> Result<()> { let entries = vec![ uuid_from_u32_quartet(a1, b1, c1, d1), uuid_from_u32_quartet(a2, b2, c2, d2), ]; - set_children_set(entries); + set_children_set(entries)?; + Ok(()) } #[no_mangle] +#[wasm_error] pub extern "C" fn set_children_3( a1: u32, b1: u32, @@ -474,16 +555,18 @@ pub extern "C" fn set_children_3( b3: u32, c3: u32, d3: u32, -) { +) -> Result<()> { let entries = vec![ uuid_from_u32_quartet(a1, b1, c1, d1), uuid_from_u32_quartet(a2, b2, c2, d2), uuid_from_u32_quartet(a3, b3, c3, d3), ]; - set_children_set(entries); + set_children_set(entries)?; + Ok(()) } #[no_mangle] +#[wasm_error] pub extern "C" fn set_children_4( a1: u32, b1: u32, @@ -501,17 +584,19 @@ pub extern "C" fn set_children_4( b4: u32, c4: u32, d4: u32, -) { +) -> Result<()> { let entries = vec![ uuid_from_u32_quartet(a1, b1, c1, d1), uuid_from_u32_quartet(a2, b2, c2, d2), uuid_from_u32_quartet(a3, b3, c3, d3), uuid_from_u32_quartet(a4, b4, c4, d4), ]; - set_children_set(entries); + set_children_set(entries)?; + Ok(()) } #[no_mangle] +#[wasm_error] pub extern "C" fn set_children_5( a1: u32, b1: u32, @@ -533,7 +618,7 @@ pub extern "C" fn set_children_5( b5: u32, c5: u32, d5: u32, -) { +) -> Result<()> { let entries = vec![ uuid_from_u32_quartet(a1, b1, c1, d1), uuid_from_u32_quartet(a2, b2, c2, d2), @@ -541,11 +626,13 @@ pub extern "C" fn set_children_5( uuid_from_u32_quartet(a4, b4, c4, d4), uuid_from_u32_quartet(a5, b5, c5, d5), ]; - set_children_set(entries); + set_children_set(entries)?; + Ok(()) } #[no_mangle] -pub extern "C" fn set_children() { +#[wasm_error] +pub extern "C" fn set_children() -> Result<()> { let bytes = mem::bytes_or_empty(); let entries: Vec = bytes @@ -553,58 +640,76 @@ pub extern "C" fn set_children() { .map(|data| Uuid::try_from(data).unwrap()) .collect(); - set_children_set(entries); + set_children_set(entries)?; if !bytes.is_empty() { - mem::free_bytes(); + mem::free_bytes()?; } + + Ok(()) } #[no_mangle] -pub extern "C" fn is_image_cached(a: u32, b: u32, c: u32, d: u32, is_thumbnail: bool) -> bool { +#[wasm_error] +pub extern "C" fn is_image_cached( + a: u32, + b: u32, + c: u32, + d: u32, + is_thumbnail: bool, +) -> Result { with_state_mut!(state, { let id = uuid_from_u32_quartet(a, b, c, d); - state.render_state().has_image(&id, is_thumbnail) + let result = state.render_state().has_image(&id, is_thumbnail); + Ok(result) }) } #[no_mangle] -pub extern "C" fn set_shape_svg_raw_content() { +#[wasm_error] +pub extern "C" fn set_shape_svg_raw_content() -> Result<()> { with_current_shape_mut!(state, |shape: &mut Shape| { let bytes = mem::bytes(); let svg_raw_content = String::from_utf8(bytes) - .unwrap() + .map_err(|e| Error::RecoverableError(e.to_string()))? .trim_end_matches('\0') .to_string(); - shape - .set_svg_raw_content(svg_raw_content) - .expect("Failed to set svg raw content"); + shape.set_svg_raw_content(svg_raw_content); }); + + Ok(()) } #[no_mangle] -pub extern "C" fn set_shape_opacity(opacity: f32) { +#[wasm_error] +pub extern "C" fn set_shape_opacity(opacity: f32) -> Result<()> { with_current_shape_mut!(state, |shape: &mut Shape| { shape.set_opacity(opacity); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_shape_hidden(hidden: bool) { +#[wasm_error] +pub extern "C" fn set_shape_hidden(hidden: bool) -> Result<()> { with_current_shape_mut!(state, |shape: &mut Shape| { shape.set_hidden(hidden); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_shape_corners(r1: f32, r2: f32, r3: f32, r4: f32) { +#[wasm_error] +pub extern "C" fn set_shape_corners(r1: f32, r2: f32, r3: f32, r4: f32) -> Result<()> { with_current_shape_mut!(state, |shape: &mut Shape| { shape.set_corners((r1, r2, r3, r4)); }); + Ok(()) } #[no_mangle] -pub extern "C" fn get_selection_rect() -> *mut u8 { +#[wasm_error] +pub extern "C" fn get_selection_rect() -> Result<*mut u8> { let bytes = mem::bytes(); let entries: Vec = bytes @@ -619,40 +724,41 @@ pub extern "C" fn get_selection_rect() -> *mut u8 { }) .collect(); - with_state_mut!(state, { + let result_bound = with_state_mut!(state, { let bbs: Vec<_> = entries .iter() .flat_map(|id| state.shapes.get(id).map(|b| b.bounds())) .collect(); - let result_bound = if bbs.len() == 1 { + if bbs.len() == 1 { bbs[0] } else { Bounds::join_bounds(&bbs) - }; + } + }); - let width = result_bound.width(); - let height = result_bound.height(); - let center = result_bound.center(); - let transform = result_bound.transform_matrix().unwrap_or(Matrix::default()); + let width = result_bound.width(); + let height = result_bound.height(); + let center = result_bound.center(); + let transform = result_bound.transform_matrix().unwrap_or(Matrix::default()); - let mut bytes = vec![0; 40]; - bytes[0..4].clone_from_slice(&width.to_le_bytes()); - bytes[4..8].clone_from_slice(&height.to_le_bytes()); - bytes[8..12].clone_from_slice(¢er.x.to_le_bytes()); - bytes[12..16].clone_from_slice(¢er.y.to_le_bytes()); - bytes[16..20].clone_from_slice(&transform[0].to_le_bytes()); - bytes[20..24].clone_from_slice(&transform[3].to_le_bytes()); - bytes[24..28].clone_from_slice(&transform[1].to_le_bytes()); - bytes[28..32].clone_from_slice(&transform[4].to_le_bytes()); - bytes[32..36].clone_from_slice(&transform[2].to_le_bytes()); - bytes[36..40].clone_from_slice(&transform[5].to_le_bytes()); - mem::write_bytes(bytes) - }) + let mut bytes = vec![0; 40]; + bytes[0..4].clone_from_slice(&width.to_le_bytes()); + bytes[4..8].clone_from_slice(&height.to_le_bytes()); + bytes[8..12].clone_from_slice(¢er.x.to_le_bytes()); + bytes[12..16].clone_from_slice(¢er.y.to_le_bytes()); + bytes[16..20].clone_from_slice(&transform[0].to_le_bytes()); + bytes[20..24].clone_from_slice(&transform[3].to_le_bytes()); + bytes[24..28].clone_from_slice(&transform[1].to_le_bytes()); + bytes[28..32].clone_from_slice(&transform[4].to_le_bytes()); + bytes[32..36].clone_from_slice(&transform[2].to_le_bytes()); + bytes[36..40].clone_from_slice(&transform[5].to_le_bytes()); + Ok(mem::write_bytes(bytes)) } #[no_mangle] -pub extern "C" fn set_structure_modifiers() { +#[wasm_error] +pub extern "C" fn set_structure_modifiers() -> Result<()> { let bytes = mem::bytes(); let entries: Vec<_> = bytes @@ -690,18 +796,22 @@ pub extern "C" fn set_structure_modifiers() { } }); - mem::free_bytes(); + mem::free_bytes()?; + Ok(()) } #[no_mangle] -pub extern "C" fn clean_modifiers() { +#[wasm_error] +pub extern "C" fn clean_modifiers() -> Result<()> { with_state_mut!(state, { state.shapes.clean_all(); }); + Ok(()) } #[no_mangle] -pub extern "C" fn set_modifiers() { +#[wasm_error] +pub extern "C" fn set_modifiers() -> Result<()> { let bytes = mem::bytes(); let entries: Vec<_> = bytes @@ -720,26 +830,31 @@ pub extern "C" fn set_modifiers() { state.set_modifiers(modifiers); state.rebuild_modifier_tiles(ids); }); + Ok(()) } #[no_mangle] -pub extern "C" fn start_temp_objects() { +#[wasm_error] +pub extern "C" fn start_temp_objects() -> Result<()> { unsafe { #[allow(static_mut_refs)] let mut state = STATE.take().expect("Got an invalid state pointer"); state = Box::new(state.start_temp_objects()); STATE = Some(state); } + Ok(()) } #[no_mangle] -pub extern "C" fn end_temp_objects() { +#[wasm_error] +pub extern "C" fn end_temp_objects() -> Result<()> { unsafe { #[allow(static_mut_refs)] let mut state = STATE.take().expect("Got an invalid state pointer"); state = Box::new(state.end_temp_objects()); STATE = Some(state); } + Ok(()) } fn main() { diff --git a/render-wasm/src/mem.rs b/render-wasm/src/mem.rs index 54cf4aa9d1..d03cb4fdc1 100644 --- a/render-wasm/src/mem.rs +++ b/render-wasm/src/mem.rs @@ -1,29 +1,29 @@ -use std::alloc::{alloc, Layout}; -use std::ptr; use std::sync::Mutex; -const LAYOUT_ALIGN: usize = 4; +use crate::error::{Error, Result, CRITICAL_ERROR}; -static BUFFERU8: Mutex>> = Mutex::new(None); +pub const LAYOUT_ALIGN: usize = 4; + +pub static BUFFERU8: Mutex>> = Mutex::new(None); +pub static BUFFER_ERROR: Mutex = Mutex::new(0x00); + +pub fn clear_error_code() { + let mut guard = BUFFER_ERROR.lock().unwrap(); + *guard = 0x00; +} + +/// Sets the error buffer from a byte. Used by #[wasm_error] when E: Into. +pub fn set_error_code(code: u8) { + let mut guard = BUFFER_ERROR.lock().unwrap(); + *guard = code; +} #[no_mangle] -pub extern "C" fn alloc_bytes(len: usize) -> *mut u8 { - let mut guard = BUFFERU8.lock().unwrap(); - - if guard.is_some() { - panic!("Bytes already allocated"); - } - - unsafe { - let layout = Layout::from_size_align_unchecked(len, LAYOUT_ALIGN); - let ptr = alloc(layout); - if ptr.is_null() { - panic!("Allocation failed"); - } - // TODO: Maybe this could be removed. - ptr::write_bytes(ptr, 0, len); - *guard = Some(Vec::from_raw_parts(ptr, len, len)); - ptr +pub extern "C" fn read_error_code() -> u8 { + if let Ok(guard) = BUFFER_ERROR.lock() { + *guard + } else { + CRITICAL_ERROR } } @@ -40,13 +40,6 @@ pub fn write_bytes(mut bytes: Vec) -> *mut u8 { ptr } -#[no_mangle] -pub extern "C" fn free_bytes() { - let mut guard = BUFFERU8.lock().unwrap(); - *guard = None; - std::mem::drop(guard); -} - pub fn bytes() -> Vec { let mut guard = BUFFERU8.lock().unwrap(); guard.take().expect("Buffer is not initialized") @@ -57,6 +50,15 @@ pub fn bytes_or_empty() -> Vec { guard.take().unwrap_or_default() } +pub fn free_bytes() -> Result<()> { + let mut guard = BUFFERU8 + .lock() + .map_err(|_| Error::CriticalError("Failed to lock buffer".to_string()))?; + *guard = None; + std::mem::drop(guard); + Ok(()) +} + pub trait SerializableResult: From + Into { type BytesType; fn clone_to_slice(&self, slice: &mut [u8]); diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 8e7e1e7c99..ef12164896 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -705,9 +705,8 @@ impl Shape { self.invalidate_extrect(); } - pub fn set_svg_raw_content(&mut self, content: String) -> Result<(), String> { + pub fn set_svg_raw_content(&mut self, content: String) { self.shape_type = Type::SVGRaw(SVGRaw::from_content(content)); - Ok(()) } pub fn set_blend_mode(&mut self, mode: BlendMode) { diff --git a/render-wasm/src/wasm.rs b/render-wasm/src/wasm.rs index 47578e5b9c..c33b614cd3 100644 --- a/render-wasm/src/wasm.rs +++ b/render-wasm/src/wasm.rs @@ -3,6 +3,7 @@ pub mod blurs; pub mod fills; pub mod fonts; pub mod layouts; +pub mod mem; pub mod paths; pub mod shadows; pub mod shapes; diff --git a/render-wasm/src/wasm/fills.rs b/render-wasm/src/wasm/fills.rs index 86f0e57e8e..8513856455 100644 --- a/render-wasm/src/wasm/fills.rs +++ b/render-wasm/src/wasm/fills.rs @@ -1,4 +1,4 @@ -use macros::ToJs; +use macros::{wasm_error, ToJs}; use crate::mem; use crate::shapes; @@ -67,7 +67,8 @@ pub fn parse_fills_from_bytes(buffer: &[u8], num_fills: usize) -> Vec Result<()> { with_current_shape_mut!(state, |shape: &mut Shape| { let bytes = mem::bytes(); // The first byte contains the actual number of fills @@ -75,8 +76,9 @@ pub extern "C" fn set_shape_fills() { // Skip the first 4 bytes (header with fill count) and parse only the actual fills let fills = parse_fills_from_bytes(&bytes[4..], num_fills); shape.set_fills(fills); - mem::free_bytes(); + mem::free_bytes()?; }); + Ok(()) } #[no_mangle] diff --git a/render-wasm/src/wasm/fills/image.rs b/render-wasm/src/wasm/fills/image.rs index 9c7a5d312b..f0e5b36526 100644 --- a/render-wasm/src/wasm/fills/image.rs +++ b/render-wasm/src/wasm/fills/image.rs @@ -1,5 +1,7 @@ use crate::mem; +use macros::wasm_error; // use crate::mem::SerializableResult; +use crate::error::Error; use crate::uuid::Uuid; use crate::with_state_mut; use crate::STATE; @@ -65,7 +67,8 @@ impl TryFrom> for ShapeImageIds { } #[no_mangle] -pub extern "C" fn store_image() { +#[wasm_error] +pub extern "C" fn store_image() -> crate::error::Result<()> { let bytes = mem::bytes(); let ids = ShapeImageIds::try_from(bytes[0..IMAGE_IDS_SIZE].to_vec()).unwrap(); @@ -87,7 +90,8 @@ pub extern "C" fn store_image() { state.touch_shape(ids.shape_id); }); - mem::free_bytes(); + mem::free_bytes()?; + Ok(()) } /// Stores an image from an existing WebGL texture, avoiding re-decoding @@ -99,13 +103,17 @@ pub extern "C" fn store_image() { /// - bytes 40-43: width (i32) /// - bytes 44-47: height (i32) #[no_mangle] -pub extern "C" fn store_image_from_texture() { +#[wasm_error] +pub extern "C" fn store_image_from_texture() -> crate::error::Result<()> { let bytes = mem::bytes(); if bytes.len() < 48 { + // FIXME: Review if this should be an critical or a recoverable error. eprintln!("store_image_from_texture: insufficient data"); - mem::free_bytes(); - return; + mem::free_bytes()?; + return Err(Error::RecoverableError( + "store_image_from_texture: insufficient data".to_string(), + )); } let ids = ShapeImageIds::try_from(bytes[0..IMAGE_IDS_SIZE].to_vec()).unwrap(); @@ -139,5 +147,6 @@ pub extern "C" fn store_image_from_texture() { state.touch_shape(ids.shape_id); }); - mem::free_bytes(); + mem::free_bytes()?; + Ok(()) } diff --git a/render-wasm/src/wasm/fonts.rs b/render-wasm/src/wasm/fonts.rs index b4a604e0e5..f4723e20c3 100644 --- a/render-wasm/src/wasm/fonts.rs +++ b/render-wasm/src/wasm/fonts.rs @@ -1,4 +1,4 @@ -use macros::ToJs; +use macros::{wasm_error, ToJs}; use crate::mem; use crate::shapes::{FontFamily, FontStyle}; @@ -30,6 +30,7 @@ impl From for FontStyle { } #[no_mangle] +#[wasm_error] pub extern "C" fn store_font( a: u32, b: u32, @@ -39,7 +40,7 @@ pub extern "C" fn store_font( style: u8, is_emoji: bool, is_fallback: bool, -) { +) -> Result<()> { with_state_mut!(state, { let id = uuid_from_u32_quartet(a, b, c, d); let font_bytes = mem::bytes(); @@ -52,8 +53,9 @@ pub extern "C" fn store_font( .fonts_mut() .add(family, &font_bytes, is_emoji, is_fallback); - mem::free_bytes(); + mem::free_bytes()?; }); + Ok(()) } #[no_mangle] diff --git a/render-wasm/src/wasm/layouts/grid.rs b/render-wasm/src/wasm/layouts/grid.rs index 096468e514..d1a0476814 100644 --- a/render-wasm/src/wasm/layouts/grid.rs +++ b/render-wasm/src/wasm/layouts/grid.rs @@ -1,4 +1,4 @@ -use macros::ToJs; +use macros::{wasm_error, ToJs}; use crate::mem; use crate::shapes::{GridCell, GridDirection, GridTrack, GridTrackType}; @@ -7,6 +7,9 @@ use crate::{uuid_from_u32_quartet, with_current_shape_mut, with_state, with_stat use super::align; +#[allow(unused_imports)] +use crate::error::Result; + #[derive(Debug)] #[repr(C, align(1))] struct RawGridCell { @@ -168,7 +171,8 @@ pub extern "C" fn set_grid_layout_data( } #[no_mangle] -pub extern "C" fn set_grid_columns() { +#[wasm_error] +pub extern "C" fn set_grid_columns() -> Result<()> { let bytes = mem::bytes(); let entries: Vec = bytes @@ -181,11 +185,13 @@ pub extern "C" fn set_grid_columns() { shape.set_grid_columns(entries); }); - mem::free_bytes(); + mem::free_bytes()?; + Ok(()) } #[no_mangle] -pub extern "C" fn set_grid_rows() { +#[wasm_error] +pub extern "C" fn set_grid_rows() -> Result<()> { let bytes = mem::bytes(); let entries: Vec = bytes @@ -198,11 +204,13 @@ pub extern "C" fn set_grid_rows() { shape.set_grid_rows(entries); }); - mem::free_bytes(); + mem::free_bytes()?; + Ok(()) } #[no_mangle] -pub extern "C" fn set_grid_cells() { +#[wasm_error] +pub extern "C" fn set_grid_cells() -> Result<()> { let bytes = mem::bytes(); let cells: Vec = bytes @@ -215,7 +223,8 @@ pub extern "C" fn set_grid_cells() { shape.set_grid_cells(cells.into_iter().map(|raw| raw.into()).collect()); }); - mem::free_bytes(); + mem::free_bytes()?; + Ok(()) } #[no_mangle] diff --git a/render-wasm/src/wasm/mem.rs b/render-wasm/src/wasm/mem.rs new file mode 100644 index 0000000000..8f6b7508ef --- /dev/null +++ b/render-wasm/src/wasm/mem.rs @@ -0,0 +1,38 @@ +use std::alloc::{alloc, Layout}; +use std::ptr; + +#[allow(unused_imports)] +use crate::error::{Error, Result}; +use crate::mem::{BUFFERU8, LAYOUT_ALIGN}; +use macros::wasm_error; + +#[no_mangle] +#[wasm_error] +pub extern "C" fn alloc_bytes(len: usize) -> Result<*mut u8> { + let mut guard = BUFFERU8 + .lock() + .map_err(|_| Error::CriticalError("Failed to lock buffer".to_string()))?; + + if guard.is_some() { + return Err(Error::CriticalError("Bytes already allocated".to_string())); + } + + unsafe { + let layout = Layout::from_size_align_unchecked(len, LAYOUT_ALIGN); + let ptr = alloc(layout); + if ptr.is_null() { + return Err(Error::CriticalError("Allocation failed".to_string())); + } + // TODO: Maybe this could be removed. + ptr::write_bytes(ptr, 0, len); + *guard = Some(Vec::from_raw_parts(ptr, len, len)); + Ok(ptr) + } +} + +#[no_mangle] +#[wasm_error] +pub extern "C" fn free_bytes() -> Result<()> { + crate::mem::free_bytes()?; + Ok(()) +} diff --git a/render-wasm/src/wasm/paths.rs b/render-wasm/src/wasm/paths.rs index 815c9d2804..0748111533 100644 --- a/render-wasm/src/wasm/paths.rs +++ b/render-wasm/src/wasm/paths.rs @@ -1,5 +1,5 @@ #![allow(unused_mut, unused_variables)] -use macros::ToJs; +use macros::{wasm_error, ToJs}; use mem::SerializableResult; use std::mem::size_of; use std::sync::{Mutex, OnceLock}; @@ -161,12 +161,14 @@ pub extern "C" fn start_shape_path_buffer() { } #[no_mangle] -pub extern "C" fn set_shape_path_chunk_buffer() { +#[wasm_error] +pub extern "C" fn set_shape_path_chunk_buffer() -> Result<()> { let bytes = mem::bytes(); let buffer = get_path_upload_buffer(); let mut buffer = buffer.lock().unwrap(); buffer.extend_from_slice(&bytes); - mem::free_bytes(); + mem::free_bytes()?; + Ok(()) } #[no_mangle] diff --git a/render-wasm/src/wasm/paths/bools.rs b/render-wasm/src/wasm/paths/bools.rs index 36bd0e4440..c19791dc95 100644 --- a/render-wasm/src/wasm/paths/bools.rs +++ b/render-wasm/src/wasm/paths/bools.rs @@ -1,4 +1,4 @@ -use macros::ToJs; +use macros::{wasm_error, ToJs}; use super::RawSegmentData; use crate::math; @@ -8,6 +8,9 @@ use crate::{mem, SerializableResult}; use crate::{with_current_shape_mut, with_state, STATE}; use std::mem::size_of; +#[allow(unused_imports)] +use crate::error::{Error, Result}; + #[derive(Debug, Clone, Copy, PartialEq, ToJs)] #[repr(u8)] #[allow(dead_code)] @@ -43,15 +46,19 @@ pub extern "C" fn set_shape_bool_type(raw_bool_type: u8) { } #[no_mangle] -pub extern "C" fn calculate_bool(raw_bool_type: u8) -> *mut u8 { +#[wasm_error] +pub extern "C" fn calculate_bool(raw_bool_type: u8) -> Result<*mut u8> { let bytes = mem::bytes_or_empty(); let entries: Vec = bytes .chunks(size_of::<::BytesType>()) - .map(|data| Uuid::try_from(data).unwrap()) - .collect(); + .map(|data| { + // FIXME: Review if this should be an critical or a recoverable error. + Uuid::try_from(data).map_err(|_| Error::RecoverableError("Invalid UUID".to_string())) + }) + .collect::>>()?; - mem::free_bytes(); + mem::free_bytes()?; let bool_type = RawBoolType::from(raw_bool_type).into(); let result; @@ -64,5 +71,5 @@ pub extern "C" fn calculate_bool(raw_bool_type: u8) -> *mut u8 { .map(RawSegmentData::from_segment) .collect(); }); - mem::write_vec(result) + Ok(mem::write_vec(result)) } diff --git a/render-wasm/src/wasm/text.rs b/render-wasm/src/wasm/text.rs index ab4b14541e..737925b9b4 100644 --- a/render-wasm/src/wasm/text.rs +++ b/render-wasm/src/wasm/text.rs @@ -1,4 +1,4 @@ -use macros::ToJs; +use macros::{wasm_error, ToJs}; use super::{fills::RawFillData, fonts::RawFontStyle}; @@ -9,6 +9,8 @@ use crate::shapes::{ use crate::utils::{uuid_from_u32, uuid_from_u32_quartet}; use crate::{with_current_shape, with_current_shape_mut, with_state, with_state_mut, STATE}; +use crate::error::Error; + const RAW_SPAN_DATA_SIZE: usize = std::mem::size_of::(); const RAW_PARAGRAPH_DATA_SIZE: usize = std::mem::size_of::(); @@ -285,16 +287,22 @@ pub extern "C" fn clear_shape_text() { } #[no_mangle] -pub extern "C" fn set_shape_text_content() { +#[wasm_error] +pub extern "C" fn set_shape_text_content() -> crate::error::Result<()> { let bytes = mem::bytes(); with_current_shape_mut!(state, |shape: &mut Shape| { let raw_text_data = RawParagraph::try_from(&bytes).unwrap(); - if shape.add_paragraph(raw_text_data.into()).is_err() { - println!("Error with set_shape_text_content on {:?}", shape.id); - } + shape.add_paragraph(raw_text_data.into()).map_err(|_| { + Error::RecoverableError(format!( + "Error with set_shape_text_content on {:?}", + shape.id + )) + })?; }); - mem::free_bytes(); + + mem::free_bytes()?; + Ok(()) } #[no_mangle] diff --git a/render-wasm/src/wasm/text_editor.rs b/render-wasm/src/wasm/text_editor.rs index 7d2b6aac93..e0512692ea 100644 --- a/render-wasm/src/wasm/text_editor.rs +++ b/render-wasm/src/wasm/text_editor.rs @@ -1,3 +1,5 @@ +use macros::{wasm_error, ToJs}; + use crate::math::{Matrix, Point, Rect}; use crate::mem; use crate::shapes::{Paragraph, Shape, TextContent, TextPositionWithAffinity, Type, VerticalAlign}; @@ -5,7 +7,6 @@ use crate::state::TextSelection; use crate::utils::uuid_from_u32_quartet; use crate::utils::uuid_to_u32_quartet; use crate::{with_state, with_state_mut, STATE}; -use macros::ToJs; use skia_safe::Color; #[derive(PartialEq, ToJs)] @@ -263,29 +264,31 @@ pub extern "C" fn text_editor_set_cursor_from_point(x: f32, y: f32) { // TEXT OPERATIONS // ============================================================================ +// FIXME: Review if all the return Ok(()) should be Err instead. #[no_mangle] -pub extern "C" fn text_editor_insert_text() { +#[wasm_error] +pub extern "C" fn text_editor_insert_text() -> Result<()> { let bytes = crate::mem::bytes(); let text = match String::from_utf8(bytes) { - Ok(s) => s, - Err(_) => return, + Ok(text) => text, + Err(_) => return Ok(()), }; with_state_mut!(state, { if !state.text_editor_state.is_active { - return; + return Ok(()); } let Some(shape_id) = state.text_editor_state.active_shape_id else { - return; + return Ok(()); }; let Some(shape) = state.shapes.get_mut(&shape_id) else { - return; + return Ok(()); }; let Type::Text(text_content) = &mut shape.shape_type else { - return; + return Ok(()); }; let selection = state.text_editor_state.selection; @@ -316,7 +319,8 @@ pub extern "C" fn text_editor_insert_text() { state.render_state.mark_touched(shape_id); }); - crate::mem::free_bytes(); + crate::mem::free_bytes()?; + Ok(()) } #[no_mangle]