From 0a420411b915462a46e95200c039b0e181791046 Mon Sep 17 00:00:00 2001 From: Jiaxun Wei Date: Thu, 2 Jan 2025 17:44:48 +0800 Subject: [PATCH] feat: add native function callback support for toBlob --- bridge/core/api/element.cc | 23 +++- bridge/core/dom/element.cc | 88 ++++++++++++-- bridge/core/dom/element.h | 5 + bridge/include/plugin_api/element.h | 15 +++ bridge/rusty_webf_sys/src/dom/element.rs | 112 ++++++++++++++---- .../rusty_webf_sys/src/html/html_element.rs | 10 +- webf/example/rust_builder/rust/src/lib.rs | 22 ++-- 7 files changed, 227 insertions(+), 48 deletions(-) diff --git a/bridge/core/api/element.cc b/bridge/core/api/element.cc index 89b7b4ac0d..3a75a037f7 100644 --- a/bridge/core/api/element.cc +++ b/bridge/core/api/element.cc @@ -3,6 +3,27 @@ */ #include "plugin_api/element.h" +#include "core/api/exception_state.h" #include "core/dom/container_node.h" +#include "core/dom/element.h" -namespace webf {} // namespace webf \ No newline at end of file +namespace webf { + +void ElementPublicMethods::ToBlob(Element* ptr, + WebFNativeFunctionContext* callback_context, + SharedExceptionState* shared_exception_state) { + auto* element = static_cast(ptr); + auto callback_impl = WebFNativeFunction::Create(callback_context, shared_exception_state); + return element->toBlob(callback_impl, shared_exception_state->exception_state); +} + +void ElementPublicMethods::ToBlobWithDevicePixelRatio(Element* ptr, + double device_pixel_ratio, + WebFNativeFunctionContext* callback_context, + SharedExceptionState* shared_exception_state) { + auto* element = static_cast(ptr); + auto callback_impl = WebFNativeFunction::Create(callback_context, shared_exception_state); + return element->toBlob(device_pixel_ratio, callback_impl, shared_exception_state->exception_state); +} + +} // namespace webf diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index dd373e716a..3d6089c725 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -385,12 +385,12 @@ Element& Element::CloneWithoutAttributesAndChildren(Document& factory) const { return *(factory.createElement(local_name_, ASSERT_NO_EXCEPTION())); } -class ElementSnapshotReader { +class ElementSnapshotPromiseReader { public: - ElementSnapshotReader(ExecutingContext* context, - Element* element, - std::shared_ptr resolver, - double device_pixel_ratio) + ElementSnapshotPromiseReader(ExecutingContext* context, + Element* element, + std::shared_ptr resolver, + double device_pixel_ratio) : context_(context), element_(element), resolver_(std::move(resolver)), device_pixel_ratio_(device_pixel_ratio) { Start(); }; @@ -406,17 +406,17 @@ class ElementSnapshotReader { double device_pixel_ratio_; }; -void ElementSnapshotReader::Start() { +void ElementSnapshotPromiseReader::Start() { context_->FlushUICommand(element_, FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout); auto callback = [](void* ptr, double contextId, char* error, uint8_t* bytes, int32_t length) -> void { - auto* reader = static_cast(ptr); + auto* reader = static_cast(ptr); auto* context = reader->context_; reader->context_->dartIsolateContext()->dispatcher()->PostToJs( context->isDedicated(), context->contextId(), - [](ElementSnapshotReader* reader, char* error, uint8_t* bytes, int32_t length) { + [](ElementSnapshotPromiseReader* reader, char* error, uint8_t* bytes, int32_t length) { if (error != nullptr) { reader->HandleFailed(error); dart_free(error); @@ -433,7 +433,7 @@ void ElementSnapshotReader::Start() { element_->bindingObject(), device_pixel_ratio_); } -void ElementSnapshotReader::HandleSnapshot(uint8_t* bytes, int32_t length) { +void ElementSnapshotPromiseReader::HandleSnapshot(uint8_t* bytes, int32_t length) { MemberMutationScope mutation_scope{context_}; Blob* blob = Blob::Create(context_); blob->SetMineType("image/png"); @@ -441,7 +441,7 @@ void ElementSnapshotReader::HandleSnapshot(uint8_t* bytes, int32_t length) { resolver_->Resolve(blob); } -void ElementSnapshotReader::HandleFailed(const char* error) { +void ElementSnapshotPromiseReader::HandleFailed(const char* error) { MemberMutationScope mutation_scope{context_}; ExceptionState exception_state; exception_state.ThrowException(context_->ctx(), ErrorType::InternalError, error); @@ -450,6 +450,58 @@ void ElementSnapshotReader::HandleFailed(const char* error) { JS_FreeValue(context_->ctx(), exception_value); } +class ElementSnapshotNativeFunctionReader { + public: + ElementSnapshotNativeFunctionReader(ExecutingContext* context, + Element* element, + std::shared_ptr function, + double device_pixel_ratio) + : context_(context), element_(element), function_(std::move(function)), device_pixel_ratio_(device_pixel_ratio) { + Start(); + }; + + void Start(); + + private: + ExecutingContext* context_; + Element* element_; + std::shared_ptr function_{nullptr}; + double device_pixel_ratio_; +}; + +void ElementSnapshotNativeFunctionReader::Start() { + context_->FlushUICommand(element_, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout); + + auto callback = [](void* ptr, double contextId, char* error, uint8_t* bytes, int32_t length) -> void { + auto* reader = static_cast(ptr); + auto* context = reader->context_; + + reader->context_->dartIsolateContext()->dispatcher()->PostToJs( + context->isDedicated(), context->contextId(), + [](ElementSnapshotNativeFunctionReader* reader, char* error, uint8_t* bytes, int32_t length) { + if (error != nullptr) { + NativeValue error_object = Native_NewCString(error); + reader->function_->Invoke(reader->context_, 1, &error_object); + dart_free(error); + } else { + auto params = new NativeValue[2]; + params[0] = Native_NewNull(); + params[1] = Native_NewUint8Bytes(length, bytes); + reader->function_->Invoke(reader->context_, 2, params); + dart_free(bytes); + } + + reader->context_->RunRustFutureTasks(); + delete reader; + }, + reader, error, bytes, length); + }; + + context_->dartMethodPtr()->toBlob(context_->isDedicated(), this, context_->contextId(), callback, + element_->bindingObject(), device_pixel_ratio_); +} + ScriptPromise Element::toBlob(ExceptionState& exception_state) { Window* window = GetExecutingContext()->window(); double device_pixel_ratio = NativeValueConverter::FromNativeValue(window->GetBindingProperty( @@ -460,10 +512,24 @@ ScriptPromise Element::toBlob(ExceptionState& exception_state) { ScriptPromise Element::toBlob(double device_pixel_ratio, ExceptionState& exception_state) { auto resolver = ScriptPromiseResolver::Create(GetExecutingContext()); - new ElementSnapshotReader(GetExecutingContext(), this, resolver, device_pixel_ratio); + new ElementSnapshotPromiseReader(GetExecutingContext(), this, resolver, device_pixel_ratio); return resolver->Promise(); } +void Element::toBlob(const std::shared_ptr& callback, ExceptionState& exception_state) { + Window* window = GetExecutingContext()->window(); + double device_pixel_ratio = NativeValueConverter::FromNativeValue(window->GetBindingProperty( + binding_call_methods::kdevicePixelRatio, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, exception_state)); + return toBlob(device_pixel_ratio, callback, exception_state); +} + +void Element::toBlob(double device_pixel_ratio, + const std::shared_ptr& callback, + ExceptionState& exception_state) { + new ElementSnapshotNativeFunctionReader(GetExecutingContext(), this, callback, device_pixel_ratio); +} + ScriptValue Element::___testGlobalToLocal__(double x, double y, webf::ExceptionState& exception_state) { const NativeValue args[] = { NativeValueConverter::ToNativeValue(x), diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 0707eed235..a1516e57fb 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -9,6 +9,7 @@ #include "bindings/qjs/script_promise.h" #include "container_node.h" #include "core/css/inline_css_style_declaration.h" +#include "core/native/native_function.h" #include "element_data.h" #include "legacy/bounding_client_rect.h" #include "legacy/element_attributes.h" @@ -80,6 +81,10 @@ class Element : public ContainerNode { ScriptPromise toBlob(double device_pixel_ratio, ExceptionState& exception_state); ScriptPromise toBlob(ExceptionState& exception_state); + void toBlob(double device_pixel_ratio, + const std::shared_ptr& callback, + ExceptionState& exception_state); + void toBlob(const std::shared_ptr& callback, ExceptionState& exception_state); ScriptValue ___testGlobalToLocal__(double x, double y, ExceptionState& exception_state); diff --git a/bridge/include/plugin_api/element.h b/bridge/include/plugin_api/element.h index 5e9fbb4e49..b97820157a 100644 --- a/bridge/include/plugin_api/element.h +++ b/bridge/include/plugin_api/element.h @@ -14,10 +14,25 @@ class SharedExceptionState; class ExecutingContext; class Element; class Document; +typedef struct WebFNativeFunctionContext WebFNativeFunctionContext; + +using PublicElementToBlob = void (*)(Element*, WebFNativeFunctionContext*, SharedExceptionState*); +using PublicElementToBlobWithDevicePixelRatio = void (*)(Element*, + double, + WebFNativeFunctionContext*, + SharedExceptionState*); struct ElementPublicMethods : WebFPublicMethods { + static void ToBlob(Element* element, WebFNativeFunctionContext* context, SharedExceptionState* exception_state); + static void ToBlobWithDevicePixelRatio(Element* element, + double device_pixel_ratio, + WebFNativeFunctionContext* context, + SharedExceptionState* exception_state); + double version{1.0}; ContainerNodePublicMethods container_node; + PublicElementToBlob element_to_blob{ToBlob}; + PublicElementToBlobWithDevicePixelRatio element_to_blob_with_device_pixel_ratio{ToBlobWithDevicePixelRatio}; }; } // namespace webf diff --git a/bridge/rusty_webf_sys/src/dom/element.rs b/bridge/rusty_webf_sys/src/dom/element.rs index cb42d9c152..37b355870e 100644 --- a/bridge/rusty_webf_sys/src/dom/element.rs +++ b/bridge/rusty_webf_sys/src/dom/element.rs @@ -9,38 +9,97 @@ use crate::*; pub struct ElementRustMethods { pub version: c_double, pub container_node: ContainerNodeRustMethods, + pub to_blob: extern "C" fn(*const OpaquePtr, *const WebFNativeFunctionContext, *const OpaquePtr) -> c_void, + pub to_blob_with_device_pixel_ratio: extern "C" fn(*const OpaquePtr, c_double, *const WebFNativeFunctionContext, *const OpaquePtr) -> c_void, } impl RustMethods for ElementRustMethods {} -#[repr(C)] -enum ElementType { - kHTMLDIVElement, - kHTMLAnchorElement, - kHTMLHeadElement, - kHTMLBodyElement, - kHTMLHTMLElement, - kHTMLImageElement, - kHTMLLinkElement, - kHTMLScriptElement, - kHTMLTemplateElement, - kHTMLUnknownElement, - kHTMLCanvasElement, - kHTMLWidgetElement, - kHTMLButtonElement, - kHTMLFormElement, - kHTMLInputElement, - kHTMLTextAreaElement -} - pub struct Element { container_node: ContainerNode, method_pointer: *const ElementRustMethods, } -impl Element {} +impl Element { + pub fn to_blob(&self, exception_state: &ExceptionState) -> WebFNativeFuture> { + let event_target: &EventTarget = &self.container_node.node.event_target; + + let future_for_return = WebFNativeFuture::>::new(); + let future_in_callback = future_for_return.clone(); + let general_callback: WebFNativeFunction = Box::new(move |argc, argv| { + if argc == 1 { + let error_string = unsafe { (*argv).clone() }; + let error_string = error_string.to_string(); + future_in_callback.set_result(Err(error_string)); + return NativeValue::new_null(); + } + if argc == 2 { + let result = unsafe { (*argv.wrapping_add(1)).clone() }; + let value = result.to_u8_bytes(); + future_in_callback.set_result(Ok(Some(value))); + return NativeValue::new_null(); + } + println!("Invalid argument count for async storage callback"); + NativeValue::new_null() + }); + let callback_data = Box::new(WebFNativeFunctionContextData { + func: general_callback, + }); + let callback_context_data_ptr = Box::into_raw(callback_data); + let callback_context = Box::new(WebFNativeFunctionContext { + callback: invoke_webf_native_function, + free_ptr: release_webf_native_function, + ptr: callback_context_data_ptr, + }); + let callback_context_ptr = Box::into_raw(callback_context); + unsafe { + (((*self.method_pointer).to_blob))(event_target.ptr, callback_context_ptr, exception_state.ptr); + } + future_for_return + } -pub trait ElementMethods: ContainerNodeMethods {} + pub fn to_blob_with_device_pixel_ratio(&self, device_pixel_ratio: f64, exception_state: &ExceptionState) -> WebFNativeFuture> { + let event_target: &EventTarget = &self.container_node.node.event_target; + + let future_for_return = WebFNativeFuture::>::new(); + let future_in_callback = future_for_return.clone(); + let general_callback: WebFNativeFunction = Box::new(move |argc, argv| { + if argc == 1 { + let error_string = unsafe { (*argv).clone() }; + let error_string = error_string.to_string(); + future_in_callback.set_result(Err(error_string)); + return NativeValue::new_null(); + } + if argc == 2 { + let result = unsafe { (*argv.wrapping_add(1)).clone() }; + let value = result.to_u8_bytes(); + future_in_callback.set_result(Ok(Some(value))); + return NativeValue::new_null(); + } + println!("Invalid argument count for async storage callback"); + NativeValue::new_null() + }); + let callback_data = Box::new(WebFNativeFunctionContextData { + func: general_callback, + }); + let callback_context_data_ptr = Box::into_raw(callback_data); + let callback_context = Box::new(WebFNativeFunctionContext { + callback: invoke_webf_native_function, + free_ptr: release_webf_native_function, + ptr: callback_context_data_ptr, + }); + let callback_context_ptr = Box::into_raw(callback_context); + unsafe { + (((*self.method_pointer).to_blob_with_device_pixel_ratio))(event_target.ptr, device_pixel_ratio, callback_context_ptr, exception_state.ptr); + } + future_for_return + } +} + +pub trait ElementMethods: ContainerNodeMethods { + fn to_blob(&self, exception_state: &ExceptionState) -> WebFNativeFuture>; + fn to_blob_with_device_pixel_ratio(&self, device_pixel_ratio: f64, exception_state: &ExceptionState) -> WebFNativeFuture>; +} impl ContainerNodeMethods for Element {} @@ -97,4 +156,11 @@ impl EventTargetMethods for Element { } } -impl ElementMethods for Element {} +impl ElementMethods for Element { + fn to_blob(&self, exception_state: &ExceptionState) -> WebFNativeFuture> { + self.to_blob(exception_state) + } + fn to_blob_with_device_pixel_ratio(&self, device_pixel_ratio: f64, exception_state: &ExceptionState) -> WebFNativeFuture> { + self.to_blob_with_device_pixel_ratio(device_pixel_ratio, exception_state) + } +} diff --git a/bridge/rusty_webf_sys/src/html/html_element.rs b/bridge/rusty_webf_sys/src/html/html_element.rs index d8cec4ba53..fc5f313d6a 100644 --- a/bridge/rusty_webf_sys/src/html/html_element.rs +++ b/bridge/rusty_webf_sys/src/html/html_element.rs @@ -20,7 +20,15 @@ pub struct HTMLElement { pub trait HTMLElementMethods: ElementMethods {} -impl ElementMethods for HTMLElement {} +impl ElementMethods for HTMLElement { + fn to_blob(&self, exception_state: &ExceptionState) -> WebFNativeFuture> { + self.element.to_blob(exception_state) + } + + fn to_blob_with_device_pixel_ratio(&self, device_pixel_ratio: f64, exception_state: &ExceptionState) -> WebFNativeFuture> { + self.element.to_blob_with_device_pixel_ratio(device_pixel_ratio, exception_state) + } +} impl ContainerNodeMethods for HTMLElement {} diff --git a/webf/example/rust_builder/rust/src/lib.rs b/webf/example/rust_builder/rust/src/lib.rs index 638198d12d..f97c7efd7b 100644 --- a/webf/example/rust_builder/rust/src/lib.rs +++ b/webf/example/rust_builder/rust/src/lib.rs @@ -1,10 +1,12 @@ use std::cell::RefCell; use std::ffi::c_void; +use std::fs::{self, File}; +use std::io::Write; use std::rc::Rc; use webf_sys::event::Event; use webf_sys::executing_context::ExecutingContextRustMethods; use webf_sys::webf_future::FutureRuntime; -use webf_sys::{initialize_webf_api, AddEventListenerOptions, EventTargetMethods, RustValue}; +use webf_sys::{document, initialize_webf_api, AddEventListenerOptions, ElementMethods, EventTargetMethods, RustValue}; use webf_sys::element::Element; use webf_sys::node::NodeMethods; @@ -50,17 +52,7 @@ pub extern "C" fn init_webf_app(handle: RustValue) println!("Hello from Rust async context!"); - let result = async_storage_2.set_item("a", "b", &exception_state).await; - - match result { - Ok(_) => { - println!("Async Storage Set Item Success"); - }, - Err(err) => { - println!("Async Storage Set Item Failed: {:?}", err); - } - } - + async_storage_2.set_item("a", "b", &exception_state).await.unwrap(); let result = async_storage_2.get_item("a", &exception_state).await; match result { @@ -71,6 +63,12 @@ pub extern "C" fn init_webf_app(handle: RustValue) println!("Async Storage Get Item Failed: {:?}", err); } } + + let html_element = context.document().document_element(); + let blob = html_element.to_blob(&exception_state).await.unwrap().unwrap(); + + let mut file = File::create("output.png").unwrap(); + file.write_all(&blob).unwrap(); }); let runtime_run_task_callback = Box::new(move || {