diff --git a/README.md b/README.md index df2d7f721b..3de25275e7 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ WebF presents a high-performance, cutting-edge web rendering engine built on top of Flutter, empowering web applications to operate natively within the Flutter ecosystem. - **Adherence to W3C Standards:** By leveraging HTML/CSS and JavaScript, WebF renders content on Flutter, ensuring impeccable alignment with standard browser rendering. - + - **Compatible with Leading Front-End Frameworks:** Given its compliance with W3C standards, WebF seamlessly integrates with popular front-end frameworks, including [React](https://reactjs.org/) and [Vue](https://vuejs.org/). - + - **Amplify Your Web Applications with Flutter:** WebF's adaptability shines through its customization capabilities. Craft bespoke HTML elements using Flutter Widgets or enhance your application by integrating a JavaScript API sourced from any Dart library on the `pub.dev` registry. - + - **Authentic Web Development Environment:** Experience a traditional web development setting with WebF. It facilitates DOM structure inspection, CSS style evaluations, and JavaScript debugging via Chrome DevTools. - + - **Craft Once, Deploy Everywhere:** Harness the versatility of WebF to design your web application and launch it across any Flutter-compatible device. What's more, maintain the flexibility to execute your apps within Node.js or web browsers, all from a unified codebase. ## Join Our Mission @@ -26,7 +26,7 @@ If you or your team are interested in supporting us, please contact @andycall on [![Discord Shield](https://discordapp.com/api/guilds/1008119434688344134/widget.png?style=banner1)](https://discord.gg/DvUBtXZ5rK) -## The update and maintenance policy for WebF versions +## The update and maintenance policy for WebF versions Each version of WebF will be maintained for the lifespan of three minor WebF releases. For instance, WebF 0.15.0 was released to be compatible with Flutter 3.10.x. Its support will conclude once WebF 0.18.0 is introduced. Any updates applied to versions 0.16.x and 0.17.x will be cherry-picked for the subsequent update of 0.15.x. diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 841b5b1505..243044fefb 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -38,6 +38,12 @@ else () add_definitions(-DENABLE_PROFILE=0) endif () +if (${ENABLE_LOG}) + add_definitions(-DENABLE_LOG=1) +else() + add_definitions(-DENABLE_LOG=0) +endif() + if(NOT MSVC) if (${CMAKE_BUILD_TYPE} STREQUAL "Release" OR ${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo") include(CheckIPOSupported) @@ -91,10 +97,10 @@ if (DEFINED PLATFORM) endif() list(APPEND BRIDGE_SOURCE - webf_bridge.cc foundation/logging.cc foundation/native_string.cc foundation/ui_task_queue.cc + foundation/shared_ui_command.cc foundation/inspector_task_queue.cc foundation/task_queue.cc foundation/string_view.cc @@ -102,6 +108,8 @@ list(APPEND BRIDGE_SOURCE foundation/native_type.cc foundation/ui_command_buffer.cc polyfill/dist/polyfill.cc + multiple_threading/dispatcher.cc + multiple_threading/looper.cc ${CMAKE_CURRENT_LIST_DIR}/third_party/dart/include/dart_api_dl.c ) @@ -189,6 +197,11 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") third_party/quickjs/src/core/builtins/js-symbol.c third_party/quickjs/src/core/builtins/js-typed-array.c ) + + if (${CMAKE_BUILD_TYPE} MATCHES "Debug") + add_definitions(-DDDEBUG) + endif () + if(${STATIC_QUICKJS}) add_library(quickjs STATIC ${QUICK_JS_SOURCE}) else() @@ -247,6 +260,8 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") bindings/qjs/rejected_promises.cc bindings/qjs/union_base.cc # Core sources + webf_bridge.cc + core/api/api.cc core/executing_context.cc core/script_forbidden_scope.cc core/script_state.cc diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 44967e37a8..7f1dab538f 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -30,6 +30,10 @@ ScriptValue ScriptWrappable::ToValue() { return ScriptValue(ctx_, jsObject_); } +multi_threading::Dispatcher* ScriptWrappable::GetDispatcher() const { + return context_->dartIsolateContext()->dispatcher().get(); +} + /// This callback will be called when QuickJS GC is running at marking stage. /// Users of this class should override `void TraceMember(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func)` to /// tell GC which member of their class should be collected by GC. @@ -238,6 +242,8 @@ void ScriptWrappable::InitializeQuickJSObject() { desc->value = return_value; desc->getter = JS_NULL; desc->setter = JS_NULL; + } else { + JS_FreeValue(ctx, return_value); } return true; } @@ -252,6 +258,8 @@ void ScriptWrappable::InitializeQuickJSObject() { desc->value = return_value; desc->getter = JS_NULL; desc->setter = JS_NULL; + } else { + JS_FreeValue(ctx, return_value); } return true; } diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 6263d915a3..c6d5fe176b 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -9,6 +9,7 @@ #include #include "bindings/qjs/cppgc/garbage_collected.h" #include "foundation/macros.h" +#include "multiple_threading/dispatcher.h" #include "wrapper_type_info.h" namespace webf { @@ -52,6 +53,7 @@ class ScriptWrappable : public GarbageCollected { ScriptValue ToValue(); FORCE_INLINE ExecutingContext* GetExecutingContext() const { return context_; }; + multi_threading::Dispatcher* GetDispatcher() const; FORCE_INLINE JSContext* ctx() const { return ctx_; } FORCE_INLINE JSRuntime* runtime() const { return runtime_; } FORCE_INLINE int64_t contextId() const { return context_id_; } diff --git a/bridge/core/api/api.cc b/bridge/core/api/api.cc new file mode 100644 index 0000000000..8aa4deb1a0 --- /dev/null +++ b/bridge/core/api/api.cc @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "api.h" +#include "core/dart_isolate_context.h" +#include "core/html/parser/html_parser.h" +#include "core/page.h" +#include "multiple_threading/dispatcher.h" + +namespace webf { + +static void ReturnEvaluateScriptsInternal(Dart_PersistentHandle persistent_handle, + EvaluateQuickjsByteCodeCallback result_callback, + bool is_success) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle, is_success ? 1 : 0); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void evaluateScriptsInternal(void* page_, + const char* code, + uint64_t code_len, + uint8_t** parsed_bytecodes, + uint64_t* bytecode_len, + const char* bundleFilename, + int32_t startLine, + Dart_Handle persistent_handle, + EvaluateScriptsCallback result_callback) { + auto page = reinterpret_cast(page_); + assert(std::this_thread::get_id() == page->currentThread()); + bool is_success = page->evaluateScript(code, code_len, parsed_bytecodes, bytecode_len, bundleFilename, startLine); + page->dartIsolateContext()->dispatcher()->PostToDart(page->isDedicated(), ReturnEvaluateScriptsInternal, + persistent_handle, result_callback, is_success); +} + +static void ReturnEvaluateQuickjsByteCodeResultToDart(Dart_PersistentHandle persistent_handle, + EvaluateQuickjsByteCodeCallback result_callback, + bool is_success) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle, is_success ? 1 : 0); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void evaluateQuickjsByteCodeInternal(void* page_, + uint8_t* bytes, + int32_t byteLen, + Dart_PersistentHandle persistent_handle, + EvaluateQuickjsByteCodeCallback result_callback) { + auto page = reinterpret_cast(page_); + assert(std::this_thread::get_id() == page->currentThread()); + bool is_success = page->evaluateByteCode(bytes, byteLen); + page->dartIsolateContext()->dispatcher()->PostToDart(page->isDedicated(), ReturnEvaluateQuickjsByteCodeResultToDart, + persistent_handle, result_callback, is_success); +} + +void parseHTMLInternal(void* page_, const char* code, int32_t length) { + auto page = reinterpret_cast(page_); + assert(std::this_thread::get_id() == page->currentThread()); + page->parseHTML(code, length); + delete code; +} + +static void ReturnInvokeEventResultToDart(Dart_Handle persistent_handle, + InvokeModuleEventCallback result_callback, + webf::NativeValue* result) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle, result); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void invokeModuleEventInternal(void* page_, + void* module_name, + const char* eventType, + void* event, + void* extra, + Dart_Handle persistent_handle, + InvokeModuleEventCallback result_callback) { + auto page = reinterpret_cast(page_); + auto dart_isolate_context = page->executingContext()->dartIsolateContext(); + assert(std::this_thread::get_id() == page->currentThread()); + auto* result = page->invokeModuleEvent(reinterpret_cast(module_name), eventType, event, + reinterpret_cast(extra)); + dart_isolate_context->dispatcher()->PostToDart(page->isDedicated(), ReturnInvokeEventResultToDart, persistent_handle, + result_callback, result); +} + +} // namespace webf diff --git a/bridge/core/api/api.h b/bridge/core/api/api.h new file mode 100644 index 0000000000..35f87269e5 --- /dev/null +++ b/bridge/core/api/api.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_API_API_H_ +#define WEBF_CORE_API_API_H_ + +#include +#include "include/webf_bridge.h" + +namespace webf { + +void evaluateScriptsInternal(void* page_, + const char* code, + uint64_t code_len, + uint8_t** parsed_bytecodes, + uint64_t* bytecode_len, + const char* bundleFilename, + int32_t startLine, + Dart_Handle dart_handle, + EvaluateScriptsCallback result_callback); + +void evaluateQuickjsByteCodeInternal(void* page_, + uint8_t* bytes, + int32_t byteLen, + Dart_PersistentHandle persistent_handle, + EvaluateQuickjsByteCodeCallback result_callback); +void parseHTMLInternal(void* page_, const char* code, int32_t length); + +void invokeModuleEventInternal(void* page_, + void* module_name, + const char* eventType, + void* event, + void* extra, + Dart_Handle dart_handle, + InvokeModuleEventCallback result_callback); + +} // namespace webf + +#endif // WEBF_CORE_API_API_H_ diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 1a5633cb37..0cb6458680 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -12,21 +12,57 @@ #include "core/executing_context.h" #include "foundation/native_string.h" #include "foundation/native_value_converter.h" +#include "logging.h" namespace webf { -void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_object, - NativeValue* return_value, +static void ReturnEventResultToDart(Dart_Handle persistent_handle, + NativeValue* result, + DartInvokeResultCallback result_callback) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle, result); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +static void HandleCallFromDartSideWrapper(NativeBindingObject* binding_object, + NativeValue* method, + int32_t argc, + NativeValue* argv, + Dart_Handle dart_object, + DartInvokeResultCallback result_callback) { + if (binding_object->disposed_) + return; + + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_object); + auto dart_isolate = binding_object->binding_target_->GetExecutingContext()->dartIsolateContext(); + auto is_dedicated = binding_object->binding_target_->GetExecutingContext()->isDedicated(); + auto context_id = binding_object->binding_target_->contextId(); + + dart_isolate->dispatcher()->PostToJs(is_dedicated, context_id, NativeBindingObject::HandleCallFromDartSide, + dart_isolate, binding_object, method, argc, argv, persistent_handle, + result_callback); +} + +NativeBindingObject::NativeBindingObject(BindingObject* target) + : binding_target_(target), invoke_binding_methods_from_dart(HandleCallFromDartSideWrapper) {} + +void NativeBindingObject::HandleCallFromDartSide(DartIsolateContext* dart_isolate_context, + NativeBindingObject* binding_object, NativeValue* native_method, int32_t argc, NativeValue* argv, - Dart_Handle dart_object) { + Dart_PersistentHandle dart_object, + DartInvokeResultCallback result_callback) { AtomicString method = AtomicString( binding_object->binding_target_->ctx(), std::unique_ptr(reinterpret_cast(native_method->u.ptr))); NativeValue result = binding_object->binding_target_->HandleCallFromDartSide(method, argc, argv, dart_object); - if (return_value != nullptr) - *return_value = result; + + auto* return_value = new NativeValue(); + std::memcpy(return_value, &result, sizeof(NativeValue)); + + dart_isolate_context->dispatcher()->PostToDart(binding_object->binding_target_->GetExecutingContext()->isDedicated(), + ReturnEventResultToDart, dart_object, return_value, result_callback); } BindingObject::BindingObject(JSContext* ctx) : ScriptWrappable(ctx), binding_object_(new NativeBindingObject(this)) {} @@ -47,7 +83,7 @@ BindingObject::~BindingObject() { BindingObject::BindingObject(JSContext* ctx, NativeBindingObject* native_binding_object) : ScriptWrappable(ctx) { native_binding_object->binding_target_ = this; - native_binding_object->invoke_binding_methods_from_dart = NativeBindingObject::HandleCallFromDartSide; + native_binding_object->invoke_binding_methods_from_dart = HandleCallFromDartSideWrapper; binding_object_ = native_binding_object; } @@ -69,50 +105,100 @@ NativeValue BindingObject::HandleCallFromDartSide(const AtomicString& method, NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, int32_t argc, const NativeValue* argv, + uint32_t reason, ExceptionState& exception_state) const { - GetExecutingContext()->FlushUICommand(); - if (binding_object_->invoke_bindings_methods_from_native == nullptr) { - exception_state.ThrowException(GetExecutingContext()->ctx(), ErrorType::InternalError, - "Failed to call dart method: invoke_bindings_methods_from_native not initialized."); - return Native_NewNull(); - } - + GetExecutingContext()->FlushUICommand(this, reason); NativeValue return_value = Native_NewNull(); NativeValue native_method = NativeValueConverter::ToNativeValue(GetExecutingContext()->ctx(), method); - binding_object_->invoke_bindings_methods_from_native(GetExecutingContext()->contextId(), binding_object_, - &return_value, &native_method, argc, argv); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Call Begin"; +#endif + + GetDispatcher()->PostToDartSync( + GetExecutingContext()->isDedicated(), contextId(), + [&](bool cancel, double contextId, const NativeBindingObject* binding_object, NativeValue* return_value, + NativeValue* method, int32_t argc, const NativeValue* argv) { + if (cancel) + return; + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Callback Start"; +#endif + + if (binding_object_->invoke_bindings_methods_from_native == nullptr) { + WEBF_LOG(DEBUG) << "invoke_bindings_methods_from_native is nullptr" << std::endl; + return; + } + binding_object_->invoke_bindings_methods_from_native(contextId, binding_object, return_value, method, argc, + argv); +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Callback End"; +#endif + }, + GetExecutingContext()->contextId(), binding_object_, &return_value, &native_method, argc, argv); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Call End"; +#endif return return_value; } NativeValue BindingObject::InvokeBindingMethod(BindingMethodCallOperations binding_method_call_operation, size_t argc, const NativeValue* argv, + uint32_t reason, ExceptionState& exception_state) const { - GetExecutingContext()->FlushUICommand(); - if (binding_object_->invoke_bindings_methods_from_native == nullptr) { - exception_state.ThrowException(GetExecutingContext()->ctx(), ErrorType::InternalError, - "Failed to call dart method: invoke_bindings_methods_from_native not initialized."); - return Native_NewNull(); - } - + GetExecutingContext()->FlushUICommand(this, reason); NativeValue return_value = Native_NewNull(); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Call Begin"; +#endif + NativeValue native_method = NativeValueConverter::ToNativeValue(binding_method_call_operation); - binding_object_->invoke_bindings_methods_from_native(GetExecutingContext()->contextId(), binding_object_, - &return_value, &native_method, argc, argv); + GetDispatcher()->PostToDartSync( + GetExecutingContext()->isDedicated(), contextId(), + [&](bool cancel, double contextId, const NativeBindingObject* binding_object, NativeValue* return_value, + NativeValue* method, int32_t argc, const NativeValue* argv) { + if (cancel) + return; + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Callback Start"; +#endif + + if (binding_object_->invoke_bindings_methods_from_native == nullptr) { + WEBF_LOG(DEBUG) << "invoke_bindings_methods_from_native is nullptr" << std::endl; + return; + } + binding_object_->invoke_bindings_methods_from_native(contextId, binding_object, return_value, method, argc, + argv); +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Callback End"; +#endif + }, + GetExecutingContext()->contextId(), binding_object_, &return_value, &native_method, argc, argv); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: PostToDartSync method: InvokeBindingMethod; Call End"; +#endif + return return_value; } -NativeValue BindingObject::GetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const { +NativeValue BindingObject::GetBindingProperty(const AtomicString& prop, + uint32_t reason, + ExceptionState& exception_state) const { if (UNLIKELY(binding_object_->disposed_)) { exception_state.ThrowException( ctx(), ErrorType::InternalError, "Can not get binding property on BindingObject, dart binding object had been disposed"); return Native_NewNull(); } - GetExecutingContext()->FlushUICommand(); const NativeValue argv[] = {Native_NewString(prop.ToNativeString(GetExecutingContext()->ctx()).release())}; - return InvokeBindingMethod(BindingMethodCallOperations::kGetProperty, 1, argv, exception_state); + return InvokeBindingMethod(BindingMethodCallOperations::kGetProperty, 1, argv, reason, exception_state); } NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, @@ -128,16 +214,17 @@ NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, if (auto element = const_cast(DynamicTo(this))) { if (std::shared_ptr recipients = MutationObserverInterestGroup::CreateForAttributesMutation(*element, prop)) { - NativeValue old_native_value = GetBindingProperty(prop, exception_state); + NativeValue old_native_value = + GetBindingProperty(prop, FlushUICommandReason::kDependentsOnElement, exception_state); ScriptValue old_value = ScriptValue(ctx(), old_native_value); recipients->EnqueueMutationRecord( MutationRecord::CreateAttributes(element, prop, AtomicString::Null(), old_value.ToString(ctx()))); } } - GetExecutingContext()->FlushUICommand(); const NativeValue argv[] = {Native_NewString(prop.ToNativeString(GetExecutingContext()->ctx()).release()), value}; - return InvokeBindingMethod(BindingMethodCallOperations::kSetProperty, 2, argv, exception_state); + return InvokeBindingMethod(BindingMethodCallOperations::kSetProperty, 2, argv, + FlushUICommandReason::kDependentsOnElement, exception_state); } ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, @@ -163,8 +250,9 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, return ScriptValue::Empty(ctx); } - NativeValue result = event_target->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, - arguments.size(), arguments.data(), exception_state); + NativeValue result = + event_target->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, arguments.size(), + arguments.data(), FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { event_target->GetExecutingContext()->HandleException(exception_state); @@ -175,7 +263,7 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, void BindingObject::HandleAnonymousAsyncCalledFromDart(void* ptr, NativeValue* native_value, - int32_t contextId, + double contextId, const char* errmsg) { auto* promise_context = static_cast(ptr); if (!promise_context->context->IsContextValid()) @@ -201,6 +289,16 @@ void BindingObject::HandleAnonymousAsyncCalledFromDart(void* ptr, delete promise_context; } +static void HandleAnonymousAsyncCalledFromDartWrapper(void* ptr, + NativeValue* native_value, + double contextId, + const char* errmsg) { + auto* promise_context = static_cast(ptr); + promise_context->context->dartIsolateContext()->dispatcher()->PostToJs( + promise_context->context->isDedicated(), contextId, BindingObject::HandleAnonymousAsyncCalledFromDart, + promise_context, native_value, contextId, errmsg); +} + ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, const ScriptValue& this_val, uint32_t argc, @@ -220,11 +318,11 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, arguments.emplace_back(NativeValueConverter::ToNativeValue(data->method_name)); arguments.emplace_back( - NativeValueConverter::ToNativeValue(event_target->GetExecutingContext()->contextId())); + NativeValueConverter::ToNativeValue(event_target->GetExecutingContext()->contextId())); arguments.emplace_back( NativeValueConverter>::ToNativeValue(promise_context)); arguments.emplace_back(NativeValueConverter>::ToNativeValue( - reinterpret_cast(HandleAnonymousAsyncCalledFromDart))); + reinterpret_cast(HandleAnonymousAsyncCalledFromDartWrapper))); ExceptionState exception_state; @@ -233,7 +331,7 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, } event_target->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, arguments.data(), - exception_state); + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { event_target->GetExecutingContext()->HandleException(exception_state); @@ -244,8 +342,8 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, } NativeValue BindingObject::GetAllBindingPropertyNames(ExceptionState& exception_state) const { - GetExecutingContext()->FlushUICommand(); - return InvokeBindingMethod(BindingMethodCallOperations::kGetAllPropertyNames, 0, nullptr, exception_state); + return InvokeBindingMethod(BindingMethodCallOperations::kGetAllPropertyNames, 0, nullptr, + FlushUICommandReason::kDependentsOnElement, exception_state); } void BindingObject::Trace(GCVisitor* visitor) const { diff --git a/bridge/core/binding_object.h b/bridge/core/binding_object.h index 265d9968a5..3bded23de2 100644 --- a/bridge/core/binding_object.h +++ b/bridge/core/binding_object.h @@ -11,6 +11,7 @@ #include #include "bindings/qjs/atomic_string.h" #include "bindings/qjs/script_wrappable.h" +#include "core/dart_methods.h" #include "foundation/native_type.h" #include "foundation/native_value.h" @@ -21,37 +22,41 @@ struct NativeBindingObject; class ExceptionState; class GCVisitor; class ScriptPromiseResolver; +class DartIsolateContext; -using InvokeBindingsMethodsFromNative = void (*)(int32_t contextId, +using InvokeBindingsMethodsFromNative = void (*)(double contextId, const NativeBindingObject* binding_object, NativeValue* return_value, NativeValue* method, int32_t argc, const NativeValue* argv); +using DartInvokeResultCallback = void (*)(Dart_Handle dart_object, NativeValue* result); + using InvokeBindingMethodsFromDart = void (*)(NativeBindingObject* binding_object, - NativeValue* return_value, NativeValue* method, int32_t argc, NativeValue* argv, - Dart_Handle dart_object); + Dart_Handle dart_object, + DartInvokeResultCallback result_callback); struct NativeBindingObject : public DartReadable { NativeBindingObject() = delete; - explicit NativeBindingObject(BindingObject* target) - : binding_target_(target), invoke_binding_methods_from_dart(HandleCallFromDartSide){}; + explicit NativeBindingObject(BindingObject* target); - static void HandleCallFromDartSide(NativeBindingObject* binding_object, - NativeValue* return_value, + static void HandleCallFromDartSide(DartIsolateContext* dart_isolate_context, + NativeBindingObject* binding_object, NativeValue* method, int32_t argc, NativeValue* argv, - Dart_Handle dart_object); + Dart_PersistentHandle dart_object, + DartInvokeResultCallback result_callback); bool disposed_{false}; BindingObject* binding_target_{nullptr}; InvokeBindingMethodsFromDart invoke_binding_methods_from_dart{nullptr}; InvokeBindingsMethodsFromNative invoke_bindings_methods_from_native{nullptr}; + void* extra{nullptr}; }; enum BindingMethodCallOperations { @@ -89,7 +94,7 @@ class BindingObject : public ScriptWrappable { void* private_data); static void HandleAnonymousAsyncCalledFromDart(void* ptr, NativeValue* native_value, - int32_t contextId, + double contextId, const char* errmsg); BindingObject() = delete; @@ -105,8 +110,9 @@ class BindingObject : public ScriptWrappable { NativeValue InvokeBindingMethod(const AtomicString& method, int32_t argc, const NativeValue* args, + uint32_t reason, ExceptionState& exception_state) const; - NativeValue GetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const; + NativeValue GetBindingProperty(const AtomicString& prop, uint32_t reason, ExceptionState& exception_state) const; NativeValue SetBindingProperty(const AtomicString& prop, NativeValue value, ExceptionState& exception_state) const; NativeValue GetAllBindingPropertyNames(ExceptionState& exception_state) const; @@ -132,6 +138,7 @@ class BindingObject : public ScriptWrappable { NativeValue InvokeBindingMethod(BindingMethodCallOperations binding_method_call_operation, size_t argc, const NativeValue* args, + uint32_t reason, ExceptionState& exception_state) const; // NativeBindingObject may allocated at Dart side. Binding this with Dart allocated NativeBindingObject. diff --git a/bridge/core/css/computed_css_style_declaration.cc b/bridge/core/css/computed_css_style_declaration.cc index d436b5fcbb..ac331e22ca 100644 --- a/bridge/core/css/computed_css_style_declaration.cc +++ b/bridge/core/css/computed_css_style_declaration.cc @@ -22,7 +22,9 @@ ScriptValue ComputedCssStyleDeclaration::item(const AtomicString& key, Exception NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), key)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kgetPropertyValue, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod( + binding_call_methods::kgetPropertyValue, 1, arguments, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, exception_state); return ScriptValue(ctx(), NativeValueConverter::FromNativeValue(ctx(), std::move(result))); } @@ -32,7 +34,9 @@ bool ComputedCssStyleDeclaration::SetItem(const AtomicString& key, NativeValue arguments[] = { NativeValueConverter::ToNativeValue(ctx(), key), NativeValueConverter::ToNativeValue(ctx(), value.ToLegacyDOMString(ctx()))}; - InvokeBindingMethod(binding_call_methods::ksetProperty, 2, arguments, exception_state); + InvokeBindingMethod(binding_call_methods::ksetProperty, 2, arguments, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, + exception_state); return true; } @@ -41,7 +45,9 @@ bool ComputedCssStyleDeclaration::DeleteItem(const webf::AtomicString& key, webf } int64_t ComputedCssStyleDeclaration::length() const { - NativeValue result = GetBindingProperty(binding_call_methods::klength, ASSERT_NO_EXCEPTION()); + NativeValue result = GetBindingProperty( + binding_call_methods::klength, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, ASSERT_NO_EXCEPTION()); return NativeValueConverter::FromNativeValue(result); } @@ -57,19 +63,25 @@ void ComputedCssStyleDeclaration::setProperty(const AtomicString& key, AtomicString ComputedCssStyleDeclaration::removeProperty(const AtomicString& key, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), key)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kremoveProperty, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod( + binding_call_methods::kremoveProperty, 1, arguments, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, exception_state); return NativeValueConverter::FromNativeValue(ctx(), std::move(result)); } bool ComputedCssStyleDeclaration::NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), key)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kcheckCSSProperty, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod( + binding_call_methods::kcheckCSSProperty, 1, arguments, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, exception_state); return NativeValueConverter::FromNativeValue(result); } void ComputedCssStyleDeclaration::NamedPropertyEnumerator(std::vector& names, ExceptionState& exception_state) { - NativeValue result = InvokeBindingMethod(binding_call_methods::kgetFullCSSPropertyList, 0, nullptr, exception_state); + NativeValue result = InvokeBindingMethod( + binding_call_methods::kgetFullCSSPropertyList, 0, nullptr, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, exception_state); auto&& arr = NativeValueConverter>::FromNativeValue(ctx(), result); for (auto& i : arr) { names.emplace_back(i); diff --git a/bridge/core/css/inline_css_style_declaration_test.cc b/bridge/core/css/inline_css_style_declaration_test.cc index 6e3edde0ee..c25b55f1c5 100644 --- a/bridge/core/css/inline_css_style_declaration_test.cc +++ b/bridge/core/css/inline_css_style_declaration_test.cc @@ -12,11 +12,11 @@ TEST(CSSStyleDeclaration, setStyleData) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "document.documentElement.style.backgroundColor = 'white';" "document.documentElement.style.backgroundColor = 'white';"; @@ -28,11 +28,11 @@ TEST(CSSStyleDeclaration, enumerateStyles) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "console.assert(Object.keys(document.body.style).length > 400)"; env->page()->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -42,25 +42,25 @@ TEST(CSSStyleDeclaration, supportCSSVaraible) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = R"( document.body.style.setProperty('--blue', 'lightblue'); console.assert(document.body.style['--blue'] === 'lightblue'); document.body.style.setProperty('--main-color', 'lightblue'); console.assert(document.body.style.getPropertyValue('--main-color') === 'lightblue'); )"; env->page()->evaluateScript(code, strlen(code), "vm://", 0); - UICommandItem* data = context->uiCommandBuffer()->data(); + UICommandItem* buffer = static_cast(context->uiCommandBuffer()->data()); size_t commandSize = context->uiCommandBuffer()->size(); - UICommandItem& last = data[commandSize - 1]; + UICommandItem& last = buffer[commandSize - 2]; EXPECT_EQ(last.type, (int32_t)UICommand::kSetStyle); uint16_t* last_key = (uint16_t*)last.string_01; - auto native_str = new SharedNativeString(last_key, last.args_01_length); + auto native_str = new webf::SharedNativeString(last_key, last.args_01_length); EXPECT_STREQ(AtomicString(context->ctx(), std::unique_ptr(static_cast(native_str))) .ToStdString(context->ctx()) @@ -74,11 +74,11 @@ TEST(CSSStyleDeclaration, supportHyphen) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "document.body.style.setProperty('background-color', 'lightblue'); " "console.assert(document.body.style.backgroundColor == 'lightblue');" @@ -92,11 +92,11 @@ TEST(InlineCSSStyleDeclaration, setNullValue) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "document.body.style.height = null;" "console.assert(document.body.style.height === '')"; diff --git a/bridge/core/dart_context_data.cc b/bridge/core/dart_context_data.cc index d68c9e71ab..657aa081ee 100644 --- a/bridge/core/dart_context_data.cc +++ b/bridge/core/dart_context_data.cc @@ -6,15 +6,21 @@ namespace webf { -const WidgetElementShape* DartContextData::GetWidgetElementShape(const AtomicString& key) { +const WidgetElementShape* DartContextData::GetWidgetElementShape(const std::string& key) { + if (widget_element_shapes_.count(key) == 0) + return nullptr; + assert(widget_element_shapes_.count(key) > 0); + std::unique_lock lock(context_data_mutex_); return widget_element_shapes_[key].get(); } -bool DartContextData::HasWidgetElementShape(const AtomicString& key) { +bool DartContextData::HasWidgetElementShape(const std::string& key) { + std::unique_lock lock(context_data_mutex_); return widget_element_shapes_.count(key) > 0; } -void DartContextData::SetWidgetElementShape(const AtomicString& key, const std::shared_ptr& shape) { +void DartContextData::SetWidgetElementShape(const std::string& key, const std::shared_ptr& shape) { + std::unique_lock lock(context_data_mutex_); widget_element_shapes_[key] = shape; } diff --git a/bridge/core/dart_context_data.h b/bridge/core/dart_context_data.h index 1437ba7591..50ef109387 100644 --- a/bridge/core/dart_context_data.h +++ b/bridge/core/dart_context_data.h @@ -5,6 +5,8 @@ #ifndef WEBF_CORE_DART_CONTEXT_DATA_H_ #define WEBF_CORE_DART_CONTEXT_DATA_H_ +#include +#include #include #include #include "bindings/qjs/atomic_string.h" @@ -12,23 +14,24 @@ namespace webf { struct WidgetElementShape { - std::set built_in_properties_; - std::set built_in_methods_; - std::set built_in_async_methods_; + std::set built_in_properties_; + std::set built_in_methods_; + std::set built_in_async_methods_; }; class DartContextData { public: - const WidgetElementShape* GetWidgetElementShape(const AtomicString& key); - bool HasWidgetElementShape(const AtomicString& key); - void SetWidgetElementShape(const AtomicString& key, const std::shared_ptr& shape); + const WidgetElementShape* GetWidgetElementShape(const std::string& key); + bool HasWidgetElementShape(const std::string& key); + void SetWidgetElementShape(const std::string& key, const std::shared_ptr& shape); private: + std::mutex context_data_mutex_; // WidgetElements' properties and methods are defined in the dart Side. // When a new kind of WidgetElement first created, Dart code will sync properties and methods to C++ code to generate // prop getter and setter and functions for JS code. This map store the properties and methods of WidgetElement which // already created. - std::unordered_map, AtomicString::KeyHasher> widget_element_shapes_; + std::unordered_map> widget_element_shapes_; }; } // namespace webf diff --git a/bridge/core/dart_isolate_context.cc b/bridge/core/dart_isolate_context.cc index 05cb54f4cb..f1fc7d9d5a 100644 --- a/bridge/core/dart_isolate_context.cc +++ b/bridge/core/dart_isolate_context.cc @@ -7,6 +7,8 @@ #include "defined_properties_initializer.h" #include "event_factory.h" #include "html_element_factory.h" +#include "logging.h" +#include "multiple_threading/looper.h" #include "names_installer.h" #include "page.h" #include "svg_element_factory.h" @@ -15,6 +17,21 @@ namespace webf { thread_local std::set alive_wires; +PageGroup::~PageGroup() { + for (auto page : pages_) { + delete page; + } +} + +void PageGroup::AddNewPage(webf::WebFPage* new_page) { + assert(std::find(pages_.begin(), pages_.end(), new_page) == pages_.end()); + pages_.push_back(new_page); +} + +void PageGroup::RemovePage(webf::WebFPage* page) { + pages_.erase(std::find(pages_.begin(), pages_.end(), page)); +} + void WatchDartWire(DartWireContext* wire) { alive_wires.emplace(wire); } @@ -42,9 +59,9 @@ const std::unique_ptr& DartIsolateContext::EnsureData() const { return data_; } -thread_local JSRuntime* DartIsolateContext::runtime_{nullptr}; +thread_local JSRuntime* runtime_{nullptr}; +thread_local uint32_t running_dart_isolates = 0; thread_local bool is_name_installed_ = false; -thread_local int64_t running_isolates_ = 0; void InitializeBuiltInStrings(JSContext* ctx) { if (!is_name_installed_) { @@ -53,14 +70,10 @@ void InitializeBuiltInStrings(JSContext* ctx) { } } -DartIsolateContext::DartIsolateContext(const uint64_t* dart_methods, int32_t dart_methods_length) - : is_valid_(true), - running_thread_(std::this_thread::get_id()), - dart_method_ptr_(std::make_unique(dart_methods, dart_methods_length)) { - if (runtime_ == nullptr) { - runtime_ = JS_NewRuntime(); - } - running_isolates_++; +void DartIsolateContext::InitializeJSRuntime() { + if (runtime_ != nullptr) + return; + runtime_ = JS_NewRuntime(); // Avoid stack overflow when running in multiple threads. JS_UpdateStackTop(runtime_); // Bump up the built-in classId. To make sure the created classId are larger than JS_CLASS_CUSTOM_CLASS_INIT_COUNT. @@ -68,39 +81,166 @@ DartIsolateContext::DartIsolateContext(const uint64_t* dart_methods, int32_t dar JSClassID id{0}; JS_NewClassID(&id); } +} + +void DartIsolateContext::FinalizeJSRuntime() { + if (running_dart_isolates > 0) + return; + + // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. + names_installer::Dispose(); + HTMLElementFactory::Dispose(); + SVGElementFactory::Dispose(); + EventFactory::Dispose(); + ClearUpWires(); + JS_TurnOnGC(runtime_); + JS_FreeRuntime(runtime_); + runtime_ = nullptr; + is_name_installed_ = false; +} + +DartIsolateContext::DartIsolateContext(const uint64_t* dart_methods, int32_t dart_methods_length) + : is_valid_(true), + running_thread_(std::this_thread::get_id()), + dart_method_ptr_(std::make_unique(this, dart_methods, dart_methods_length)) { is_valid_ = true; + running_dart_isolates++; + InitializeJSRuntime(); } -DartIsolateContext::~DartIsolateContext() { - is_valid_ = false; - pages_.clear(); - running_isolates_--; +JSRuntime* DartIsolateContext::runtime() { + return runtime_; +} - if (running_isolates_ == 0) { - // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. - names_installer::Dispose(); - HTMLElementFactory::Dispose(); - SVGElementFactory::Dispose(); - EventFactory::Dispose(); - ClearUpWires(); +DartIsolateContext::~DartIsolateContext() {} + +void DartIsolateContext::Dispose(multi_threading::Callback callback) { + dispatcher_->Dispose([this, &callback]() { + is_valid_ = false; data_.reset(); - JS_FreeRuntime(runtime_); - runtime_ = nullptr; - is_name_installed_ = false; + pages_in_ui_thread_.clear(); + running_dart_isolates--; + FinalizeJSRuntime(); + callback(); + }); +} + +void DartIsolateContext::InitializeNewPageInJSThread(PageGroup* page_group, + DartIsolateContext* dart_isolate_context, + double page_context_id, + Dart_Handle dart_handle, + AllocateNewPageCallback result_callback) { + DartIsolateContext::InitializeJSRuntime(); + auto* page = new WebFPage(dart_isolate_context, true, page_context_id, nullptr); + dart_isolate_context->dispatcher_->PostToDart(true, HandleNewPageResult, page_group, dart_handle, result_callback, + page); +} + +void DartIsolateContext::DisposePageAndKilledJSThread(DartIsolateContext* dart_isolate_context, + WebFPage* page, + int thread_group_id, + Dart_Handle dart_handle, + DisposePageCallback result_callback) { + delete page; + dart_isolate_context->dispatcher_->PostToDart(true, HandleDisposePageAndKillJSThread, dart_isolate_context, + thread_group_id, dart_handle, result_callback); +} + +void DartIsolateContext::DisposePageInJSThread(DartIsolateContext* dart_isolate_context, + WebFPage* page, + Dart_Handle dart_handle, + DisposePageCallback result_callback) { + delete page; + dart_isolate_context->dispatcher_->PostToDart(true, HandleDisposePage, dart_handle, result_callback); +} + +void* DartIsolateContext::AddNewPage(double thread_identity, + Dart_Handle dart_handle, + AllocateNewPageCallback result_callback) { + bool is_in_flutter_ui_thread = thread_identity < 0; + assert(is_in_flutter_ui_thread == false); + + int thread_group_id = static_cast(thread_identity); + + PageGroup* page_group; + if (!dispatcher_->IsThreadGroupExist(thread_group_id)) { + dispatcher_->AllocateNewJSThread(thread_group_id); + page_group = new PageGroup(); + dispatcher_->SetOpaqueForJSThread(thread_group_id, page_group, [](void* p) { + delete static_cast(p); + DartIsolateContext::FinalizeJSRuntime(); + }); + } else { + page_group = static_cast(dispatcher_->GetOpaque(thread_group_id)); } + + dispatcher_->PostToJs(true, thread_group_id, InitializeNewPageInJSThread, page_group, this, thread_identity, + dart_handle, result_callback); + return nullptr; } -void DartIsolateContext::AddNewPage(std::unique_ptr&& new_page) { - pages_.insert(std::move(new_page)); +void* DartIsolateContext::AddNewPageSync(double thread_identity) { + auto page = std::make_unique(this, false, thread_identity, nullptr); + void* p = page.get(); + pages_in_ui_thread_.emplace(std::move(page)); + return p; +} + +void DartIsolateContext::HandleNewPageResult(PageGroup* page_group, + Dart_Handle persistent_handle, + AllocateNewPageCallback result_callback, + WebFPage* new_page) { + page_group->AddNewPage(new_page); + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle, new_page); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void DartIsolateContext::HandleDisposePage(Dart_Handle persistent_handle, DisposePageCallback result_callback) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void DartIsolateContext::HandleDisposePageAndKillJSThread(DartIsolateContext* dart_isolate_context, + int thread_group_id, + Dart_Handle persistent_handle, + DisposePageCallback result_callback) { + dart_isolate_context->dispatcher_->KillJSThreadSync(thread_group_id); + + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +void DartIsolateContext::RemovePage(double thread_identity, + WebFPage* page, + Dart_Handle dart_handle, + DisposePageCallback result_callback) { + bool is_in_flutter_ui_thread = thread_identity < 0; + assert(is_in_flutter_ui_thread == false); + + int thread_group_id = static_cast(page->contextId()); + auto page_group = static_cast(dispatcher_->GetOpaque(thread_group_id)); + + page_group->RemovePage(page); + + if (page_group->Empty()) { + page->executingContext()->SetContextInValid(); + dispatcher_->PostToJs(true, thread_group_id, DisposePageAndKilledJSThread, this, page, thread_group_id, dart_handle, + result_callback); + } else { + dispatcher_->PostToJs(true, thread_group_id, DisposePageInJSThread, this, page, dart_handle, result_callback); + } } -void DartIsolateContext::RemovePage(const webf::WebFPage* page) { - for (auto it = pages_.begin(); it != pages_.end(); ++it) { +void DartIsolateContext::RemovePageSync(double thread_identity, WebFPage* page) { + for (auto it = pages_in_ui_thread_.begin(); it != pages_in_ui_thread_.end(); ++it) { if (it->get() == page) { - pages_.erase(it); + pages_in_ui_thread_.erase(it); break; } } } -} // namespace webf \ No newline at end of file +} // namespace webf diff --git a/bridge/core/dart_isolate_context.h b/bridge/core/dart_isolate_context.h index 5108b60c09..8ee9ec7e26 100644 --- a/bridge/core/dart_isolate_context.h +++ b/bridge/core/dart_isolate_context.h @@ -9,12 +9,26 @@ #include "bindings/qjs/script_value.h" #include "dart_context_data.h" #include "dart_methods.h" +#include "multiple_threading/dispatcher.h" namespace webf { class WebFPage; class DartIsolateContext; +class PageGroup { + public: + ~PageGroup(); + void AddNewPage(WebFPage* new_page); + void RemovePage(WebFPage* page); + bool Empty() { return pages_.empty(); } + + std::vector* pages() { return &pages_; }; + + private: + std::vector pages_; +}; + struct DartWireContext { ScriptValue jsObject; }; @@ -30,26 +44,56 @@ class DartIsolateContext { public: explicit DartIsolateContext(const uint64_t* dart_methods, int32_t dart_methods_length); - FORCE_INLINE JSRuntime* runtime() { return runtime_; } - FORCE_INLINE bool valid() { return is_valid_ && std::this_thread::get_id() == running_thread_; } - FORCE_INLINE const std::unique_ptr& dartMethodPtr() const { - assert(std::this_thread::get_id() == running_thread_); - return dart_method_ptr_; + JSRuntime* runtime(); + FORCE_INLINE bool valid() { return is_valid_; } + FORCE_INLINE DartMethodPointer* dartMethodPtr() const { return dart_method_ptr_.get(); } + FORCE_INLINE const std::unique_ptr& dispatcher() const { return dispatcher_; } + FORCE_INLINE void SetDispatcher(std::unique_ptr&& dispatcher) { + dispatcher_ = std::move(dispatcher); } const std::unique_ptr& EnsureData() const; - void AddNewPage(std::unique_ptr&& new_page); - void RemovePage(const WebFPage* page); + void* AddNewPage(double thread_identity, Dart_Handle dart_handle, AllocateNewPageCallback result_callback); + void* AddNewPageSync(double thread_identity); + void RemovePage(double thread_identity, WebFPage* page, Dart_Handle dart_handle, DisposePageCallback result_callback); + void RemovePageSync(double thread_identity, WebFPage* page); ~DartIsolateContext(); + void Dispose(multi_threading::Callback callback); private: + static void InitializeJSRuntime(); + static void FinalizeJSRuntime(); + static void InitializeNewPageInJSThread(PageGroup* page_group, + DartIsolateContext* dart_isolate_context, + double page_context_id, + Dart_Handle dart_handle, + AllocateNewPageCallback result_callback); + static void DisposePageAndKilledJSThread(DartIsolateContext* dart_isolate_context, + WebFPage* page, + int thread_group_id, + Dart_Handle dart_handle, + DisposePageCallback result_callback); + static void DisposePageInJSThread(DartIsolateContext* dart_isolate_context, + WebFPage* page, + Dart_Handle dart_handle, + DisposePageCallback result_callback); + static void HandleNewPageResult(PageGroup* page_group, + Dart_Handle persistent_handle, + AllocateNewPageCallback result_callback, + WebFPage* new_page); + static void HandleDisposePage(Dart_Handle persistent_handle, DisposePageCallback result_callback); + static void HandleDisposePageAndKillJSThread(DartIsolateContext* dart_isolate_context, + int thread_group_id, + Dart_Handle persistent_handle, + DisposePageCallback result_callback); + int is_valid_{false}; - std::set> pages_; std::thread::id running_thread_; mutable std::unique_ptr data_; - static thread_local JSRuntime* runtime_; + std::set> pages_in_ui_thread_; + std::unique_ptr dispatcher_ = nullptr; // Dart methods ptr should keep alive when ExecutingContext is disposing. const std::unique_ptr dart_method_ptr_ = nullptr; }; diff --git a/bridge/core/dart_methods.cc b/bridge/core/dart_methods.cc index 96c77d919f..18ca6dd8ae 100644 --- a/bridge/core/dart_methods.cc +++ b/bridge/core/dart_methods.cc @@ -5,32 +5,337 @@ #include "dart_methods.h" #include +#include "dart_isolate_context.h" +#include "foundation/native_type.h" + +using namespace webf; namespace webf { -webf::DartMethodPointer::DartMethodPointer(const uint64_t* dart_methods, int32_t dart_methods_length) { +int32_t start_timer_id = 1; + +DartMethodPointer::DartMethodPointer(DartIsolateContext* dart_isolate_context, + const uint64_t* dart_methods, + int32_t dart_methods_length) + : dart_isolate_context_(dart_isolate_context) { size_t i = 0; - invokeModule = reinterpret_cast(dart_methods[i++]); - requestBatchUpdate = reinterpret_cast(dart_methods[i++]); - reloadApp = reinterpret_cast(dart_methods[i++]); - setTimeout = reinterpret_cast(dart_methods[i++]); - setInterval = reinterpret_cast(dart_methods[i++]); - clearTimeout = reinterpret_cast(dart_methods[i++]); - requestAnimationFrame = reinterpret_cast(dart_methods[i++]); - cancelAnimationFrame = reinterpret_cast(dart_methods[i++]); - toBlob = reinterpret_cast(dart_methods[i++]); - flushUICommand = reinterpret_cast(dart_methods[i++]); - create_binding_object = reinterpret_cast(dart_methods[i++]); - -#if ENABLE_PROFILE - getPerformanceEntries = reinterpret_cast(dart_methods[i++]); -#else - i++; -#endif - - onJsError = reinterpret_cast(dart_methods[i++]); - onJsLog = reinterpret_cast(dart_methods[i++]); + invoke_module_ = reinterpret_cast(dart_methods[i++]); + request_batch_update_ = reinterpret_cast(dart_methods[i++]); + reload_app_ = reinterpret_cast(dart_methods[i++]); + set_timeout_ = reinterpret_cast(dart_methods[i++]); + set_interval_ = reinterpret_cast(dart_methods[i++]); + clear_timeout_ = reinterpret_cast(dart_methods[i++]); + request_animation_frame_ = reinterpret_cast(dart_methods[i++]); + cancel_animation_frame_ = reinterpret_cast(dart_methods[i++]); + to_blob_ = reinterpret_cast(dart_methods[i++]); + flush_ui_command_ = reinterpret_cast(dart_methods[i++]); + create_binding_object_ = reinterpret_cast(dart_methods[i++]); + get_widget_element_shape_ = reinterpret_cast(dart_methods[i++]); + on_js_error_ = reinterpret_cast(dart_methods[i++]); + on_js_log_ = reinterpret_cast(dart_methods[i++]); assert_m(i == dart_methods_length, "Dart native methods count is not equal with C++ side method registrations."); } + +NativeValue* DartMethodPointer::invokeModule(bool is_dedicated, + void* callback_context, + double context_id, + SharedNativeString* moduleName, + SharedNativeString* method, + NativeValue* params, + AsyncModuleCallback callback) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::invokeModule callSync START"; +#endif + NativeValue* result = dart_isolate_context_->dispatcher()->PostToDartSync( + is_dedicated, context_id, + [&](bool cancel, void* callback_context, double context_id, SharedNativeString* moduleName, + SharedNativeString* method, NativeValue* params, AsyncModuleCallback callback) -> webf::NativeValue* { + if (cancel) + return nullptr; + return invoke_module_(callback_context, context_id, moduleName, method, params, callback); + }, + callback_context, context_id, moduleName, method, params, callback); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::invokeModule callSync END"; +#endif + + return result; +} + +void DartMethodPointer::requestBatchUpdate(bool is_dedicated, double context_id) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::requestBatchUpdate Call"; +#endif + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, request_batch_update_, context_id); +} + +void DartMethodPointer::reloadApp(bool is_dedicated, double context_id) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::reloadApp Call"; +#endif + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, reload_app_, context_id); +} + +int32_t DartMethodPointer::setTimeout(bool is_dedicated, + void* callback_context, + double context_id, + AsyncCallback callback, + int32_t timeout) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::setTimeout callSync START"; +#endif + + int32_t new_timer_id = start_timer_id++; + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, set_timeout_, new_timer_id, callback_context, + context_id, callback, timeout); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::setTimeout callSync END"; +#endif + + return new_timer_id; +} + +int32_t DartMethodPointer::setInterval(bool is_dedicated, + void* callback_context, + double context_id, + AsyncCallback callback, + int32_t timeout) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::setInterval callSync START"; +#endif + + int32_t new_timer_id = start_timer_id++; + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, set_interval_, new_timer_id, callback_context, + context_id, callback, timeout); +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::setInterval callSync END"; +#endif + return new_timer_id; +} + +void DartMethodPointer::clearTimeout(bool is_dedicated, double context_id, int32_t timer_id) { +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[CPP] ClearTimeoutWrapper call" << std::endl; +#endif + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, clear_timeout_, context_id, timer_id); +} + +int32_t DartMethodPointer::requestAnimationFrame(bool is_dedicated, + void* callback_context, + double context_id, + AsyncRAFCallback callback) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::requestAnimationFrame call START"; +#endif + + int32_t new_frame_id = start_timer_id++; + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, request_animation_frame_, new_frame_id, + callback_context, context_id, callback); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::requestAnimationFrame call END"; +#endif + return new_frame_id; +} + +void DartMethodPointer::cancelAnimationFrame(bool is_dedicated, double context_id, int32_t id) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::cancelAnimationFrame call START"; +#endif + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, cancel_animation_frame_, context_id, id); +} + +void DartMethodPointer::toBlob(bool is_dedicated, + void* callback_context, + double context_id, + AsyncBlobCallback blobCallback, + void* element_ptr, + double devicePixelRatio) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::toBlob call START"; +#endif + + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, to_blob_, callback_context, context_id, blobCallback, + element_ptr, devicePixelRatio); +} + +void DartMethodPointer::flushUICommand(bool is_dedicated, + double context_id, + void* native_binding_object, + uint32_t reason) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::flushUICommand SYNC call START"; +#endif + + dart_isolate_context_->dispatcher()->PostToDartSync( + is_dedicated, context_id, + [&](bool cancel, double context_id, void* native_binding_object, uint32_t reason) -> void { + if (cancel) + return; + + flush_ui_command_(context_id, native_binding_object, reason); + }, + context_id, native_binding_object, reason); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::flushUICommand SYNC call END"; +#endif +} + +void DartMethodPointer::createBindingObject(bool is_dedicated, + double context_id, + void* native_binding_object, + int32_t type, + void* args, + int32_t argc) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::createBindingObject SYNC call START"; +#endif + + dart_isolate_context_->dispatcher()->PostToDartSync( + is_dedicated, context_id, + [&](bool cancel, double context_id, void* native_binding_object, int32_t type, void* args, int32_t argc) -> void { + if (cancel) + return; + create_binding_object_(context_id, native_binding_object, type, args, argc); + }, + context_id, native_binding_object, type, args, argc); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::createBindingObject SYNC call END"; +#endif +} + +bool DartMethodPointer::getWidgetElementShape(bool is_dedicated, + double context_id, + void* native_binding_object, + NativeValue* value) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::getWidgetElementShape SYNC call START"; +#endif + + int8_t is_success = dart_isolate_context_->dispatcher()->PostToDartSync( + is_dedicated, context_id, + [&](bool cancel, double context_id, void* native_binding_object, NativeValue* value) -> int8_t { + if (cancel) + return 0; + return get_widget_element_shape_(context_id, native_binding_object, value); + }, + context_id, native_binding_object, value); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::getWidgetElementShape SYNC call END"; +#endif + + return is_success == 1; +} + +void DartMethodPointer::onJSError(bool is_dedicated, double context_id, const char* error) { + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, on_js_error_, context_id, error); +} + +void DartMethodPointer::onJSLog(bool is_dedicated, double context_id, int32_t level, const char* log) { + if (on_js_log_ == nullptr) + return; + + char* log_str = (char*)dart_malloc(sizeof(char) * strlen(log)); + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, on_js_log_, context_id, level, log_str); +} + +void DartMethodPointer::matchImageSnapshot(bool is_dedicated, + void* callback_context, + double context_id, + uint8_t* bytes, + int32_t length, + SharedNativeString* name, + MatchImageSnapshotCallback callback) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::createBindingObject call START"; +#endif + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, match_image_snapshot_, callback_context, context_id, + bytes, length, name, callback); +} + +void DartMethodPointer::matchImageSnapshotBytes(bool is_dedicated, + void* callback_context, + double context_id, + uint8_t* image_a_bytes, + int32_t image_a_size, + uint8_t* image_b_bytes, + int32_t image_b_size, + MatchImageSnapshotCallback callback) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::matchImageSnapshotBytes call START"; +#endif + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, match_image_snapshot_bytes_, callback_context, + context_id, image_a_bytes, image_a_size, image_b_bytes, image_b_size, + callback); +} + +const char* DartMethodPointer::environment(bool is_dedicated, double context_id) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::environment callSync START"; +#endif + const char* result = + dart_isolate_context_->dispatcher()->PostToDartSync(is_dedicated, context_id, [&](bool cancel) -> const char* { + if (cancel) + return nullptr; + return environment_(); + }); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher] DartMethodPointer::environment callSync END"; +#endif + + return result; +} + +void DartMethodPointer::simulatePointer(bool is_dedicated, + void* ptr, + MousePointer* mouse_pointer, + int32_t length, + int32_t pointer, + AsyncCallback async_callback) { + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, simulate_pointer_, ptr, mouse_pointer, length, pointer, + async_callback); +} + +void DartMethodPointer::simulateInputText(bool is_dedicated, SharedNativeString* nativeString) { + dart_isolate_context_->dispatcher()->PostToDart(is_dedicated, simulate_input_text_, nativeString); +} + +void DartMethodPointer::SetOnJSError(webf::OnJSError func) { + on_js_error_ = func; +} + +void DartMethodPointer::SetMatchImageSnapshot(MatchImageSnapshot func) { + match_image_snapshot_ = func; +} + +void DartMethodPointer::SetMatchImageSnapshotBytes(MatchImageSnapshotBytes func) { + match_image_snapshot_bytes_ = func; +} + +void DartMethodPointer::SetEnvironment(Environment func) { + environment_ = func; +} + +void DartMethodPointer::SetSimulateInputText(SimulateInputText func) { + simulate_input_text_ = func; +} + +void DartMethodPointer::SetSimulatePointer(SimulatePointer func) { + simulate_pointer_ = func; +} + } // namespace webf diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index 05826f353b..bc119f48c6 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -13,6 +13,7 @@ #include #include "foundation/native_string.h" #include "foundation/native_value.h" +#include "include/dart_api.h" #if defined(_WIN32) #define WEBF_EXPORT_C extern "C" __declspec(dllexport) @@ -24,47 +25,63 @@ namespace webf { -using AsyncCallback = void (*)(void* callback_context, int32_t context_id, const char* errmsg); -using AsyncRAFCallback = void (*)(void* callback_context, int32_t context_id, double result, const char* errmsg); +using InvokeModuleResultCallback = void (*)(Dart_PersistentHandle persistent_handle, NativeValue* result); +using AsyncCallback = void (*)(void* callback_context, double context_id, char* errmsg); +using AsyncRAFCallback = void (*)(void* callback_context, double context_id, double result, char* errmsg); using AsyncModuleCallback = NativeValue* (*)(void* callback_context, - int32_t context_id, + double context_id, const char* errmsg, - NativeValue* value); + NativeValue* value, + Dart_PersistentHandle persistent_handle, + InvokeModuleResultCallback result_callback); + using AsyncBlobCallback = - void (*)(void* callback_context, int32_t context_id, const char* error, uint8_t* bytes, int32_t length); + void (*)(void* callback_context, double context_id, char* error, uint8_t* bytes, int32_t length); typedef NativeValue* (*InvokeModule)(void* callback_context, - int32_t context_id, + double context_id, SharedNativeString* moduleName, SharedNativeString* method, NativeValue* params, AsyncModuleCallback callback); -typedef void (*RequestBatchUpdate)(int32_t context_id); -typedef void (*ReloadApp)(int32_t context_id); -typedef int32_t (*SetTimeout)(void* callback_context, int32_t context_id, AsyncCallback callback, int32_t timeout); -typedef int32_t (*SetInterval)(void* callback_context, int32_t context_id, AsyncCallback callback, int32_t timeout); -typedef int32_t (*RequestAnimationFrame)(void* callback_context, int32_t context_id, AsyncRAFCallback callback); -typedef void (*ClearTimeout)(int32_t context_id, int32_t timerId); -typedef void (*CancelAnimationFrame)(int32_t context_id, int32_t id); +typedef void (*RequestBatchUpdate)(double context_id); +typedef void (*ReloadApp)(double context_id); +typedef void (*SetTimeout)(int32_t new_timer_id, + void* callback_context, + double context_id, + AsyncCallback callback, + int32_t timeout); +typedef void (*SetInterval)(int32_t new_timer_id, + void* callback_context, + double context_id, + AsyncCallback callback, + int32_t timeout); +typedef void (*RequestAnimationFrame)(int32_t new_frame_id, + void* callback_context, + double context_id, + AsyncRAFCallback callback); +typedef void (*ClearTimeout)(double context_id, int32_t timerId); +typedef void (*CancelAnimationFrame)(double context_id, int32_t id); typedef void (*ToBlob)(void* callback_context, - int32_t context_id, + double context_id, AsyncBlobCallback blobCallback, void* element_ptr, double devicePixelRatio); -typedef void (*OnJSError)(int32_t context_id, const char*); -typedef void (*OnJSLog)(int32_t context_id, int32_t level, const char*); -typedef void (*FlushUICommand)(int32_t context_id); +typedef void (*OnJSError)(double context_id, const char*); +typedef void (*OnJSLog)(double context_id, int32_t level, const char*); +typedef void (*FlushUICommand)(double context_id, void* native_binding_object, uint32_t reason); typedef void ( - *CreateBindingObject)(int32_t context_id, void* native_binding_object, int32_t type, void* args, int32_t argc); + *CreateBindingObject)(double context_id, void* native_binding_object, int32_t type, void* args, int32_t argc); +typedef int8_t (*GetWidgetElementShape)(double context_id, void* native_binding_object, NativeValue* value); -using MatchImageSnapshotCallback = void (*)(void* callback_context, int32_t context_id, int8_t, const char* errmsg); +using MatchImageSnapshotCallback = void (*)(void* callback_context, double context_id, int8_t, char* errmsg); using MatchImageSnapshot = void (*)(void* callback_context, - int32_t context_id, + double context_id, uint8_t* bytes, int32_t length, SharedNativeString* name, MatchImageSnapshotCallback callback); using MatchImageSnapshotBytes = void (*)(void* callback_context, - int32_t context_id, + double context_id, uint8_t* image_a_bytes, int32_t image_a_size, uint8_t* image_b_bytes, @@ -81,7 +98,7 @@ typedef NativePerformanceEntryList* (*GetPerformanceEntries)(int32_t); #endif struct MousePointer { - int32_t context_id; + double context_id; double x; double y; double change; @@ -93,31 +110,120 @@ using SimulatePointer = void (*)(void* ptr, MousePointer*, int32_t length, int32_t pointer, AsyncCallback async_callback); using SimulateInputText = void (*)(SharedNativeString* nativeString); -struct DartMethodPointer { +enum FlushUICommandReason : uint32_t { + kStandard = 1, + kDependentsOnElement = 1 << 2, + kDependentsOnLayout = 1 << 3, + kDependentsAll = 1 << 4 +}; + +class DartIsolateContext; + +class DartMethodPointer { DartMethodPointer() = delete; - explicit DartMethodPointer(const uint64_t* dart_methods, int32_t dartMethodsLength); - - InvokeModule invokeModule{nullptr}; - RequestBatchUpdate requestBatchUpdate{nullptr}; - ReloadApp reloadApp{nullptr}; - SetTimeout setTimeout{nullptr}; - SetInterval setInterval{nullptr}; - ClearTimeout clearTimeout{nullptr}; - RequestAnimationFrame requestAnimationFrame{nullptr}; - CancelAnimationFrame cancelAnimationFrame{nullptr}; - ToBlob toBlob{nullptr}; - OnJSError onJsError{nullptr}; - OnJSLog onJsLog{nullptr}; - MatchImageSnapshot matchImageSnapshot{nullptr}; - MatchImageSnapshotBytes matchImageSnapshotBytes{nullptr}; - Environment environment{nullptr}; - SimulatePointer simulatePointer{nullptr}; - SimulateInputText simulateInputText{nullptr}; - FlushUICommand flushUICommand{nullptr}; - CreateBindingObject create_binding_object{nullptr}; -#if ENABLE_PROFILE - GetPerformanceEntries getPerformanceEntries{nullptr}; -#endif + + public: + explicit DartMethodPointer(DartIsolateContext* dart_isolate_context, + const uint64_t* dart_methods, + int32_t dartMethodsLength); + NativeValue* invokeModule(bool is_dedicated, + void* callback_context, + double context_id, + SharedNativeString* moduleName, + SharedNativeString* method, + NativeValue* params, + AsyncModuleCallback callback); + + void requestBatchUpdate(bool is_dedicated, double context_id); + void reloadApp(bool is_dedicated, double context_id); + int32_t setTimeout(bool is_dedicated, + void* callback_context, + double context_id, + AsyncCallback callback, + int32_t timeout); + int32_t setInterval(bool is_dedicated, + void* callback_context, + double context_id, + AsyncCallback callback, + int32_t timeout); + void clearTimeout(bool is_dedicated, double context_id, int32_t timerId); + int32_t requestAnimationFrame(bool is_dedicated, + void* callback_context, + double context_id, + AsyncRAFCallback callback); + void cancelAnimationFrame(bool is_dedicated, double context_id, int32_t id); + void toBlob(bool is_dedicated, + void* callback_context, + double context_id, + AsyncBlobCallback blobCallback, + void* element_ptr, + double devicePixelRatio); + void flushUICommand(bool is_dedicated, double context_id, void* native_binding_object, uint32_t reason); + void createBindingObject(bool is_dedicated, + double context_id, + void* native_binding_object, + int32_t type, + void* args, + int32_t argc); + bool getWidgetElementShape(bool is_dedicated, double context_id, void* native_binding_object, NativeValue* value); + + void onJSError(bool is_dedicated, double context_id, const char*); + void onJSLog(bool is_dedicated, double context_id, int32_t level, const char*); + + void matchImageSnapshot(bool is_dedicated, + void* callback_context, + double context_id, + uint8_t* bytes, + int32_t length, + SharedNativeString* name, + MatchImageSnapshotCallback callback); + + void matchImageSnapshotBytes(bool is_dedicated, + void* callback_context, + double context_id, + uint8_t* image_a_bytes, + int32_t image_a_size, + uint8_t* image_b_bytes, + int32_t image_b_size, + MatchImageSnapshotCallback callback); + + const char* environment(bool is_dedicated, double context_id); + void simulatePointer(bool is_dedicated, + void* ptr, + MousePointer*, + int32_t length, + int32_t pointer, + AsyncCallback async_callback); + void simulateInputText(bool is_dedicated, SharedNativeString* nativeString); + + void SetOnJSError(OnJSError func); + void SetMatchImageSnapshot(MatchImageSnapshot func); + void SetMatchImageSnapshotBytes(MatchImageSnapshotBytes func); + void SetEnvironment(Environment func); + void SetSimulatePointer(SimulatePointer func); + void SetSimulateInputText(SimulateInputText func); + + private: + DartIsolateContext* dart_isolate_context_{nullptr}; + InvokeModule invoke_module_{nullptr}; + RequestBatchUpdate request_batch_update_{nullptr}; + ReloadApp reload_app_{nullptr}; + SetTimeout set_timeout_{nullptr}; + SetInterval set_interval_{nullptr}; + ClearTimeout clear_timeout_{nullptr}; + RequestAnimationFrame request_animation_frame_{nullptr}; + CancelAnimationFrame cancel_animation_frame_{nullptr}; + ToBlob to_blob_{nullptr}; + FlushUICommand flush_ui_command_{nullptr}; + CreateBindingObject create_binding_object_{nullptr}; + GetWidgetElementShape get_widget_element_shape_{nullptr}; + OnJSError on_js_error_{nullptr}; + OnJSLog on_js_log_{nullptr}; + MatchImageSnapshot match_image_snapshot_{nullptr}; + MatchImageSnapshotBytes match_image_snapshot_bytes_{nullptr}; + Environment environment_{nullptr}; + SimulatePointer simulate_pointer_{nullptr}; + SimulateInputText simulate_input_text_{nullptr}; }; } // namespace webf diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 64f10e369a..93a042755d 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -165,7 +165,8 @@ bool Document::ChildTypeAllowed(NodeType type) const { Element* Document::querySelector(const AtomicString& selectors, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), selectors)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kquerySelector, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kquerySelector, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return nullptr; } @@ -174,7 +175,8 @@ Element* Document::querySelector(const AtomicString& selectors, ExceptionState& std::vector Document::querySelectorAll(const AtomicString& selectors, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), selectors)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kquerySelectorAll, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kquerySelectorAll, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return {}; } @@ -183,7 +185,8 @@ std::vector Document::querySelectorAll(const AtomicString& selectors, Element* Document::getElementById(const AtomicString& id, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), id)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementById, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementById, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return {}; } @@ -193,8 +196,8 @@ Element* Document::getElementById(const AtomicString& id, ExceptionState& except std::vector Document::getElementsByClassName(const AtomicString& class_name, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), class_name)}; - NativeValue result = - InvokeBindingMethod(binding_call_methods::kgetElementsByClassName, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementsByClassName, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return {}; } @@ -203,7 +206,8 @@ std::vector Document::getElementsByClassName(const AtomicString& class std::vector Document::getElementsByTagName(const AtomicString& tag_name, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), tag_name)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementsByTagName, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementsByTagName, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return {}; } @@ -212,7 +216,8 @@ std::vector Document::getElementsByTagName(const AtomicString& tag_nam std::vector Document::getElementsByName(const AtomicString& name, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), name)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementsByName, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementsByName, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return {}; } @@ -220,12 +225,12 @@ std::vector Document::getElementsByName(const AtomicString& name, Exce } Element* Document::elementFromPoint(double x, double y, ExceptionState& exception_state) { - GetExecutingContext()->FlushUICommand(); const NativeValue args[] = { NativeValueConverter::ToNativeValue(x), NativeValueConverter::ToNativeValue(y), }; - NativeValue result = InvokeBindingMethod(binding_call_methods::kelementFromPoint, 2, args, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kelementFromPoint, 2, args, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return nullptr; } @@ -237,7 +242,8 @@ Window* Document::defaultView() const { } AtomicString Document::domain() { - NativeValue dart_result = GetBindingProperty(binding_call_methods::kdomain, ASSERT_NO_EXCEPTION()); + NativeValue dart_result = GetBindingProperty(binding_call_methods::kdomain, + FlushUICommandReason::kDependentsOnElement, ASSERT_NO_EXCEPTION()); return NativeValueConverter::FromNativeValue(ctx(), std::move(dart_result)); } @@ -247,17 +253,20 @@ void Document::setDomain(const AtomicString& value, ExceptionState& exception_st } AtomicString Document::compatMode() { - NativeValue dart_result = GetBindingProperty(binding_call_methods::kcompatMode, ASSERT_NO_EXCEPTION()); + NativeValue dart_result = GetBindingProperty(binding_call_methods::kcompatMode, + FlushUICommandReason::kDependentsOnElement, ASSERT_NO_EXCEPTION()); return NativeValueConverter::FromNativeValue(ctx(), std::move(dart_result)); } AtomicString Document::readyState() { - NativeValue dart_result = GetBindingProperty(binding_call_methods::kreadyState, ASSERT_NO_EXCEPTION()); + NativeValue dart_result = GetBindingProperty(binding_call_methods::kreadyState, + FlushUICommandReason::kDependentsOnElement, ASSERT_NO_EXCEPTION()); return NativeValueConverter::FromNativeValue(ctx(), std::move(dart_result)); } bool Document::hidden() { - NativeValue dart_result = GetBindingProperty(binding_call_methods::khidden, ASSERT_NO_EXCEPTION()); + NativeValue dart_result = GetBindingProperty(binding_call_methods::khidden, + FlushUICommandReason::kDependentsOnElement, ASSERT_NO_EXCEPTION()); return NativeValueConverter::FromNativeValue(dart_result); } diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 3af1b14215..023b03c01d 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -14,11 +14,11 @@ TEST(Document, createElement) { logCalled = true; EXPECT_STREQ(message.c_str(), "
"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "console.log(div);"; @@ -34,11 +34,11 @@ TEST(Document, body) { logCalled = true; EXPECT_STREQ(message.c_str(), ""); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "console.log(document.body)"; env->page()->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -49,8 +49,8 @@ TEST(Document, appendParentWillFail) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); + auto context = env->page()->executingContext(); const char* code = "document.body.appendChild(document.documentElement)"; env->page()->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, true); @@ -64,11 +64,11 @@ TEST(Document, createTextNode) { logCalled = true; EXPECT_STREQ(message.c_str(), "
"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "div.setAttribute('hello', 1234);" @@ -88,11 +88,11 @@ TEST(Document, createComment) { logCalled = true; EXPECT_STREQ(message.c_str(), "
"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "div.setAttribute('hello', 1234);" @@ -112,11 +112,11 @@ TEST(Document, instanceofNode) { logCalled = true; EXPECT_STREQ(message.c_str(), "true true true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "console.log(document instanceof Node, document instanceof Document, document instanceof EventTarget)"; env->page()->evaluateScript(code, strlen(code), "vm://", 0); @@ -130,11 +130,11 @@ TEST(Document, FreedByOutOfScope) { webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = false; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "(() => { let img = document.createElement('div'); })();"; env->page()->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -149,15 +149,15 @@ TEST(Document, createElementShouldWorkWithMultipleContext) { const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; { - auto env = TEST_init([](int32_t contextId, const char* errmsg) {}); - auto context = env->page()->GetExecutingContext(); + auto env = TEST_init([](double contextId, const char* errmsg) {}); + auto context = env->page()->executingContext(); env->page()->evaluateScript(code, strlen(code), "vm://", 0); env_1 = env.release(); } { - auto env = TEST_init([](int32_t contextId, const char* errmsg) {}); - auto context = env->page()->GetExecutingContext(); + auto env = TEST_init([](double contextId, const char* errmsg) {}); + auto context = env->page()->executingContext(); const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; env->page()->evaluateScript(code, strlen(code), "vm://", 0); } @@ -174,11 +174,11 @@ TEST(document, all) { logCalled = true; EXPECT_STREQ(message.c_str(), "3 "); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "console.log(document.all.length, document.all[0]);" "document.body.appendChild(document.createElement('div'));" diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 7e23347b2a..a0390765ca 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -74,15 +74,17 @@ void Element::removeAttribute(const AtomicString& name, ExceptionState& exceptio } BoundingClientRect* Element::getBoundingClientRect(ExceptionState& exception_state) { - GetExecutingContext()->FlushUICommand(); - NativeValue result = InvokeBindingMethod(binding_call_methods::kgetBoundingClientRect, 0, nullptr, exception_state); + NativeValue result = InvokeBindingMethod( + binding_call_methods::kgetBoundingClientRect, 0, nullptr, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, exception_state); return BoundingClientRect::Create( GetExecutingContext(), NativeValueConverter>::FromNativeValue(result)); } std::vector Element::getClientRects(ExceptionState& exception_state) { - GetExecutingContext()->FlushUICommand(); - NativeValue result = InvokeBindingMethod(binding_call_methods::kgetClientRects, 0, nullptr, exception_state); + NativeValue result = InvokeBindingMethod( + binding_call_methods::kgetClientRects, 0, nullptr, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, exception_state); if (exception_state.HasException()) { return {}; } @@ -97,8 +99,8 @@ std::vector Element::getClientRects(ExceptionState& excepti } void Element::click(ExceptionState& exception_state) { - GetExecutingContext()->FlushUICommand(); - InvokeBindingMethod(binding_call_methods::kclick, 0, nullptr, exception_state); + InvokeBindingMethod(binding_call_methods::kclick, 0, nullptr, FlushUICommandReason::kDependentsOnElement, + exception_state); } void Element::scroll(ExceptionState& exception_state) { @@ -106,21 +108,23 @@ void Element::scroll(ExceptionState& exception_state) { } void Element::scroll(double x, double y, ExceptionState& exception_state) { - GetExecutingContext()->FlushUICommand(); const NativeValue args[] = { NativeValueConverter::ToNativeValue(x), NativeValueConverter::ToNativeValue(y), }; - InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); + InvokeBindingMethod(binding_call_methods::kscroll, 2, args, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, + exception_state); } void Element::scroll(const std::shared_ptr& options, ExceptionState& exception_state) { - GetExecutingContext()->FlushUICommand(); const NativeValue args[] = { NativeValueConverter::ToNativeValue(options->hasLeft() ? options->left() : 0.0), NativeValueConverter::ToNativeValue(options->hasTop() ? options->top() : 0.0), }; - InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); + InvokeBindingMethod(binding_call_methods::kscroll, 2, args, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, + exception_state); } void Element::scrollBy(ExceptionState& exception_state) { @@ -128,21 +132,23 @@ void Element::scrollBy(ExceptionState& exception_state) { } void Element::scrollBy(double x, double y, ExceptionState& exception_state) { - GetExecutingContext()->FlushUICommand(); const NativeValue args[] = { NativeValueConverter::ToNativeValue(x), NativeValueConverter::ToNativeValue(y), }; - InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, exception_state); + InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, + exception_state); } void Element::scrollBy(const std::shared_ptr& options, ExceptionState& exception_state) { - GetExecutingContext()->FlushUICommand(); const NativeValue args[] = { NativeValueConverter::ToNativeValue(options->hasLeft() ? options->left() : 0.0), NativeValueConverter::ToNativeValue(options->hasTop() ? options->top() : 0.0), }; - InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, exception_state); + InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, + exception_state); } void Element::scrollTo(ExceptionState& exception_state) { @@ -187,8 +193,8 @@ void Element::setId(const AtomicString& value, ExceptionState& exception_state) std::vector Element::getElementsByClassName(const AtomicString& class_name, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), class_name)}; - NativeValue result = - InvokeBindingMethod(binding_call_methods::kgetElementsByClassName, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementsByClassName, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return {}; } @@ -197,7 +203,8 @@ std::vector Element::getElementsByClassName(const AtomicString& class_ std::vector Element::getElementsByTagName(const AtomicString& tag_name, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), tag_name)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementsByTagName, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementsByTagName, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return {}; } @@ -206,7 +213,8 @@ std::vector Element::getElementsByTagName(const AtomicString& tag_name Element* Element::querySelector(const AtomicString& selectors, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), selectors)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kquerySelector, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kquerySelector, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return nullptr; } @@ -215,7 +223,8 @@ Element* Element::querySelector(const AtomicString& selectors, ExceptionState& e std::vector Element::querySelectorAll(const AtomicString& selectors, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), selectors)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kquerySelectorAll, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kquerySelectorAll, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return {}; } @@ -224,7 +233,8 @@ std::vector Element::querySelectorAll(const AtomicString& selectors, E bool Element::matches(const AtomicString& selectors, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), selectors)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kmatches, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kmatches, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return false; } @@ -233,7 +243,8 @@ bool Element::matches(const AtomicString& selectors, ExceptionState& exception_s Element* Element::closest(const AtomicString& selectors, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), selectors)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kclosest, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod(binding_call_methods::kclosest, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); if (exception_state.HasException()) { return nullptr; } @@ -380,20 +391,30 @@ class ElementSnapshotReader { }; void ElementSnapshotReader::Start() { - context_->FlushUICommand(); + context_->FlushUICommand(element_, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout); - auto callback = [](void* ptr, int32_t contextId, const char* error, uint8_t* bytes, int32_t length) -> void { + auto callback = [](void* ptr, double contextId, char* error, uint8_t* bytes, int32_t length) -> void { auto* reader = static_cast(ptr); - if (error != nullptr) { - reader->HandleFailed(error); - } else { - reader->HandleSnapshot(bytes, length); - } - delete reader; + auto* context = reader->context_; + + reader->context_->dartIsolateContext()->dispatcher()->PostToJs( + context->isDedicated(), context->contextId(), + [](ElementSnapshotReader* reader, char* error, uint8_t* bytes, int32_t length) { + if (error != nullptr) { + reader->HandleFailed(error); + dart_free(error); + } else { + reader->HandleSnapshot(bytes, length); + dart_free(bytes); + } + delete reader; + }, + reader, error, bytes, length); }; - context_->dartMethodPtr()->toBlob(this, context_->contextId(), callback, element_->bindingObject(), - device_pixel_ratio_); + context_->dartMethodPtr()->toBlob(context_->isDedicated(), this, context_->contextId(), callback, + element_->bindingObject(), device_pixel_ratio_); } void ElementSnapshotReader::HandleSnapshot(uint8_t* bytes, int32_t length) { @@ -413,8 +434,9 @@ void ElementSnapshotReader::HandleFailed(const char* error) { ScriptPromise Element::toBlob(ExceptionState& exception_state) { Window* window = GetExecutingContext()->window(); - double device_pixel_ratio = NativeValueConverter::FromNativeValue( - window->GetBindingProperty(binding_call_methods::kdevicePixelRatio, exception_state)); + double device_pixel_ratio = NativeValueConverter::FromNativeValue(window->GetBindingProperty( + binding_call_methods::kdevicePixelRatio, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, exception_state)); return toBlob(device_pixel_ratio, exception_state); } diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index afe4b438fd..03b6a218a4 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -14,17 +14,17 @@ interface Element extends Node, ParentNode, ChildNode { name: DartImpl; readonly attributes: ElementAttributes; readonly style: CSSStyleDeclaration; - readonly clientHeight: DartImpl; - readonly clientLeft: DartImpl; - readonly clientTop: DartImpl; - readonly clientWidth: DartImpl; + readonly clientHeight: DartImpl>; + readonly clientLeft: DartImpl>; + readonly clientTop: DartImpl>; + readonly clientWidth: DartImpl>; readonly outerHTML: string; innerHTML: string; readonly ownerDocument: Document; - scrollLeft: DartImpl; - scrollTop: DartImpl; - readonly scrollWidth: DartImpl; - readonly scrollHeight: DartImpl; + scrollLeft: DartImpl>; + scrollTop: DartImpl>; + readonly scrollWidth: DartImpl>; + readonly scrollHeight: DartImpl>; readonly prefix: string | null; readonly localName: string; readonly namespaceURI: string | null; @@ -32,7 +32,7 @@ interface Element extends Node, ParentNode, ChildNode { * Returns the HTML-uppercased qualified name. */ readonly tagName: string; - readonly dir: DartImpl; + dir: DartImpl; /** * Returns element's first attribute whose qualified name is qualifiedName, and null if there is no such attribute otherwise. */ diff --git a/bridge/core/dom/element_test.cc b/bridge/core/dom/element_test.cc index b989ce6198..faf135fead 100644 --- a/bridge/core/dom/element_test.cc +++ b/bridge/core/dom/element_test.cc @@ -15,11 +15,11 @@ TEST(Element, setAttribute) { logCalled = true; EXPECT_STREQ(message.c_str(), "1234"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "div.setAttribute('hello', 1234);" @@ -37,11 +37,11 @@ TEST(Element, getAttribute) { logCalled = true; EXPECT_STREQ(message.c_str(), "helloworld"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "let string = 'helloworld';" @@ -65,11 +65,11 @@ TEST(Element, setAttributeWithHTML) { logCalled = true; EXPECT_STREQ(message.c_str(), "100%"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "div.innerHTML = '
"); #endif }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); std::string code = R"( const div = document.createElement('div'); div.style.width = '100px'; @@ -119,7 +119,7 @@ TEST(Element, style) { logCalled = true; EXPECT_STREQ(message.c_str(), "true false"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -135,11 +135,11 @@ TEST(Element, instanceofNode) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "console.log(div instanceof Node)"; @@ -156,11 +156,11 @@ TEST(Element, instanceofEventTarget) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "console.log(div instanceof EventTarget)"; diff --git a/bridge/core/dom/events/custom_event_test.cc b/bridge/core/dom/events/custom_event_test.cc index 577b600d4d..a85a16bb3d 100644 --- a/bridge/core/dom/events/custom_event_test.cc +++ b/bridge/core/dom/events/custom_event_test.cc @@ -16,11 +16,11 @@ TEST(CustomEvent, instanceofEvent) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let customEvent = new CustomEvent('abc', { detail: 'helloworld'});" "console.log(customEvent instanceof Event);"; diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 6bedcfb489..cf1fb230dc 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -394,8 +394,13 @@ NativeValue EventTarget::HandleDispatchEventFromDart(int32_t argc, const NativeV }; WatchDartWire(wire); - Dart_NewFinalizableHandle_DL(dart_object, reinterpret_cast(wire), sizeof(DartWireContext), - dart_object_finalize_callback); + + GetDispatcher()->PostToDart( + GetExecutingContext()->isDedicated(), + [](Dart_Handle object, void* peer, intptr_t external_allocation_size, Dart_HandleFinalizer callback) { + Dart_NewFinalizableHandle_DL(object, peer, external_allocation_size, callback); + }, + dart_object, reinterpret_cast(wire), sizeof(DartWireContext), dart_object_finalize_callback); if (exception_state.HasException()) { JSValue error = JS_GetException(ctx()); diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index 067c4f7c9f..25f3efd99e 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -18,11 +18,11 @@ TEST(EventTarget, addEventListener) { EXPECT_STREQ(message.c_str(), "1234"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f); " "div.dispatchEvent(new Event('click'));"; @@ -35,11 +35,11 @@ TEST(EventTarget, removeEventListener) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f);" "div.removeEventListener('click', f); div.dispatchEvent(new Event('click'));"; @@ -55,12 +55,12 @@ TEST(EventTarget, setNoEventTargetProperties) { logCalled = true; EXPECT_STREQ(message.c_str(), "{name: 1}"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div'); div._a = { name: 1}; console.log(div._a); " "document.body.appendChild(div);"; @@ -75,11 +75,11 @@ TEST(EventTarget, propertyEventHandler) { logCalled = true; EXPECT_STREQ(message.c_str(), "Æ’ () 1234"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div'); " "div.onclick = function() { return 1234; };" @@ -95,11 +95,11 @@ TEST(EventTarget, overwritePropertyEventHandler) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div'); " "div.onclick = function() { return 1234; };" @@ -116,11 +116,11 @@ TEST(EventTarget, propertyEventOnWindow) { logCalled = true; EXPECT_STREQ(message.c_str(), "1234"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "window.onclick = function() { console.log(1234); };" "window.dispatchEvent(new Event('click'));"; @@ -136,11 +136,11 @@ TEST(EventTarget, asyncFunctionCallback) { logCalled = true; EXPECT_STREQ(message.c_str(), "done"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); std::string code = R"( const img = document.createElement('img'); img.style.width = '100px'; @@ -174,11 +174,11 @@ TEST(EventTarget, ClassInheritEventTarget) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); std::string code = std::string(R"( class Sample extends EventTarget { constructor() { @@ -241,12 +241,12 @@ TEST(EventTarget, shouldKeepAtom) { }; std::string code = "addEventListener('click', () => {console.log(1)});"; env->page()->evaluateScript(code.c_str(), code.size(), "internal://", 0); - JS_RunGC(JS_GetRuntime(env->page()->GetExecutingContext()->ctx())); + JS_RunGC(JS_GetRuntime(env->page()->executingContext()->ctx())); std::string code2 = "addEventListener('appear', () => {console.log(2)});"; env->page()->evaluateScript(code2.c_str(), code2.size(), "internal://", 0); - JS_RunGC(JS_GetRuntime(env->page()->GetExecutingContext()->ctx())); + JS_RunGC(JS_GetRuntime(env->page()->executingContext()->ctx())); std::string code3 = "(function() { var eeee = new Event('appear'); dispatchEvent(eeee); } )();"; env->page()->evaluateScript(code3.c_str(), code3.size(), "internal://", 0); @@ -274,6 +274,6 @@ proxy.dispatchEvent(new Event('click')); )"; env->page()->evaluateScript(code.c_str(), code.size(), "internal://", 0); - JS_RunGC(JS_GetRuntime(env->page()->GetExecutingContext()->ctx())); + JS_RunGC(JS_GetRuntime(env->page()->executingContext()->ctx())); EXPECT_EQ(logCalled, true); } \ No newline at end of file diff --git a/bridge/core/dom/events/event_test.cc b/bridge/core/dom/events/event_test.cc index 062f136a40..4bcba1caaf 100644 --- a/bridge/core/dom/events/event_test.cc +++ b/bridge/core/dom/events/event_test.cc @@ -15,7 +15,7 @@ TEST(MouseEvent, init) { EXPECT_STREQ(message.c_str(), "10"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); auto context = env->page()->getContext(); const char* code = "let mouseEvent = new MouseEvent('click', {clientX: 10, clientY: 20}); console.log(mouseEvent.clientX);"; diff --git a/bridge/core/dom/legacy/bounding_client_rect.cc b/bridge/core/dom/legacy/bounding_client_rect.cc index bdc9f03d0a..0e053c8fe5 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.cc +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -13,7 +13,8 @@ BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, Native } BoundingClientRect::BoundingClientRect(ExecutingContext* context, NativeBindingObject* native_binding_object) - : BindingObject(context->ctx(), native_binding_object) {} + : BindingObject(context->ctx(), native_binding_object), + extra_(static_cast(native_binding_object->extra)) {} NativeValue BoundingClientRect::HandleCallFromDartSide(const AtomicString& method, int32_t argc, diff --git a/bridge/core/dom/legacy/bounding_client_rect.d.ts b/bridge/core/dom/legacy/bounding_client_rect.d.ts index 49b457e4e6..26fe6df07a 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.d.ts +++ b/bridge/core/dom/legacy/bounding_client_rect.d.ts @@ -1,12 +1,12 @@ interface BoundingClientRect { - readonly x: DartImpl; - readonly y: DartImpl; - readonly width: DartImpl; - readonly height: DartImpl; - readonly top: DartImpl; - readonly right: DartImpl; - readonly bottom: DartImpl; - readonly left: DartImpl; + readonly x: double; + readonly y: double; + readonly width: double; + readonly height: double; + readonly top: double; + readonly right: double; + readonly bottom: double; + readonly left: double; new(): void; } diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index 6c9428e623..9d424661d3 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -14,6 +14,17 @@ namespace webf { class ExecutingContext; +struct BoundingClientRectData { + double x; + double y; + double width; + double height; + double top; + double right; + double bottom; + double left; +}; + class BoundingClientRect : public BindingObject { DEFINE_WRAPPERTYPEINFO(); @@ -28,24 +39,17 @@ class BoundingClientRect : public BindingObject { const NativeValue* argv, Dart_Handle dart_object) override; - double x() const { return x_; } - double y() const { return y_; } - double width() const { return width_; } - double height() const { return height_; } - double top() const { return top_; } - double right() const { return right_; } - double bottom() const { return bottom_; } - double left() const { return left_; } + double x() const { return extra_->x; } + double y() const { return extra_->y; } + double width() const { return extra_->width; } + double height() const { return extra_->height; } + double top() const { return extra_->top; } + double right() const { return extra_->right; } + double bottom() const { return extra_->bottom; } + double left() const { return extra_->left; } private: - double x_; - double y_; - double width_; - double height_; - double top_; - double right_; - double bottom_; - double left_; + BoundingClientRectData* extra_ = nullptr; }; } // namespace webf diff --git a/bridge/core/dom/legacy/element_attribute_test.cc b/bridge/core/dom/legacy/element_attribute_test.cc index 5fc5b23fde..5a59068750 100644 --- a/bridge/core/dom/legacy/element_attribute_test.cc +++ b/bridge/core/dom/legacy/element_attribute_test.cc @@ -11,11 +11,11 @@ TEST(Element, overrideAttribute) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = R"( const text = document.createElement('div'); text.setAttribute('value', 'Hello'); diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index 09be015ee5..a37eca9899 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -31,7 +31,8 @@ AtomicString ElementAttributes::getAttribute(const AtomicString& name, Exception if (attributes_.count(name) == 0) { if (element_->IsWidgetElement()) { // Fallback to directly FFI access to dart. - NativeValue dart_result = element_->GetBindingProperty(name, exception_state); + NativeValue dart_result = + element_->GetBindingProperty(name, FlushUICommandReason::kDependentsOnElement, exception_state); if (dart_result.tag == NativeTag::TAG_STRING) { return NativeValueConverter::FromNativeValue(element_->ctx(), std::move(dart_result)); } @@ -83,7 +84,8 @@ bool ElementAttributes::hasAttribute(const AtomicString& name, ExceptionState& e if (!has_attribute && element_->IsWidgetElement()) { // Fallback to directly FFI access to dart. - NativeValue dart_result = element_->GetBindingProperty(name, exception_state); + NativeValue dart_result = + element_->GetBindingProperty(name, FlushUICommandReason::kDependentsOnElement, exception_state); return dart_result.tag != NativeTag::TAG_NULL; } diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index f0f9f05b38..83ef523614 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -15,8 +15,8 @@ TEST(Node, appendChild) { EXPECT_STREQ(message.c_str(), "true true true"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "document.body.appendChild(div);" @@ -32,8 +32,8 @@ TEST(Node, MutationObserver) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); + auto context = env->page()->executingContext(); const char* code = R"( const container = document.createElement('div'); document.body.appendChild(container); @@ -81,8 +81,8 @@ TEST(Node, nodeName) { EXPECT_STREQ(message.c_str(), "DIV #text #document-fragment #comment #document"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "let text = document.createTextNode('helloworld');" @@ -102,8 +102,8 @@ TEST(Node, childNodes) { EXPECT_STREQ(message.c_str(), "true true true true"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); + auto context = env->page()->executingContext(); MemberMutationScope scope{context}; const char* code = "let div1 = document.createElement('div');" @@ -125,8 +125,8 @@ TEST(Node, textNodeHaveEmptyChildNodes) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); + auto context = env->page()->executingContext(); const char* code = "let text = document.createTextNode('helloworld');" "console.log(text.childNodes);"; @@ -143,8 +143,8 @@ TEST(Node, textContent) { EXPECT_STREQ(message.c_str(), "1234helloworld"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); + auto context = env->page()->executingContext(); const char* code = "let text1 = document.createTextNode('1234');" "let text2 = document.createTextNode('helloworld');" @@ -165,8 +165,8 @@ TEST(Node, setTextContent) { EXPECT_STREQ(message.c_str(), "1234"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "div.textContent = '1234';" @@ -184,8 +184,8 @@ TEST(Node, ensureDetached) { EXPECT_STREQ(message.c_str(), "true true"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "document.body.appendChild(div);" @@ -203,11 +203,11 @@ TEST(Node, replaceBody) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); // const char* code = "let newbody = document.createElement('body'); document.documentElement.replaceChild(newbody, // document.body)"; const char* code = "document.body = document.createElement('body');"; @@ -241,11 +241,11 @@ TEST(Node, cloneNode) { logCalled = true; EXPECT_STREQ(message.c_str(), "true true true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); env->page()->evaluateScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -290,11 +290,11 @@ TEST(Node, nestedNode) { logCalled = true; EXPECT_STREQ(message.c_str(), "true true true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); env->page()->evaluateScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -332,11 +332,11 @@ el.replaceChild(child_3, child_1); logCalled = true; EXPECT_STREQ(message.c_str(), "true true true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); env->page()->evaluateScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -358,11 +358,11 @@ console.assert(el.isConnected == false); logCalled = true; EXPECT_STREQ(message.c_str(), "true true true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); env->page()->evaluateScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(errorCalled, false); diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc index 7e804e4aed..f498234572 100644 --- a/bridge/core/dom/scripted_animation_controller.cc +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -9,7 +9,7 @@ namespace webf { -static void handleRAFTransientCallback(void* ptr, int32_t contextId, double highResTimeStamp, const char* errmsg) { +static void handleRAFTransientCallback(void* ptr, double contextId, double highResTimeStamp, char* errmsg) { auto* frame_callback = static_cast(ptr); auto* context = frame_callback->context(); @@ -19,6 +19,12 @@ static void handleRAFTransientCallback(void* ptr, int32_t contextId, double high if (errmsg != nullptr) { JSValue exception = JS_ThrowTypeError(frame_callback->context()->ctx(), "%s", errmsg); context->HandleException(&exception); + dart_free(errmsg); + return; + } + + if (frame_callback->status() == FrameCallback::FrameStatus::kCanceled) { + context->document()->script_animations()->callbackCollection()->RemoveFrameCallback(frame_callback->frameId()); return; } @@ -34,21 +40,25 @@ static void handleRAFTransientCallback(void* ptr, int32_t contextId, double high context->document()->script_animations()->callbackCollection()->RemoveFrameCallback(frame_callback->frameId()); } +static void handleRAFTransientCallbackWrapper(void* ptr, double contextId, double highResTimeStamp, char* errmsg) { + auto* frame_callback = static_cast(ptr); + auto* context = frame_callback->context(); + + if (!context->IsContextValid()) + return; + + context->dartIsolateContext()->dispatcher()->PostToJs( + context->isDedicated(), contextId, webf::handleRAFTransientCallback, ptr, contextId, highResTimeStamp, errmsg); +} + uint32_t ScriptAnimationController::RegisterFrameCallback(const std::shared_ptr& frame_callback, ExceptionState& exception_state) { auto* context = frame_callback->context(); - if (context->dartMethodPtr()->requestAnimationFrame == nullptr) { - exception_state.ThrowException( - context->ctx(), ErrorType::InternalError, - "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) is not registered."); - return -1; - } - frame_callback->SetStatus(FrameCallback::FrameStatus::kPending); - uint32_t requestId = context->dartMethodPtr()->requestAnimationFrame(frame_callback.get(), context->contextId(), - handleRAFTransientCallback); + uint32_t requestId = context->dartMethodPtr()->requestAnimationFrame( + context->isDedicated(), frame_callback.get(), context->contextId(), handleRAFTransientCallbackWrapper); frame_callback->SetFrameId(requestId); // Register frame callback to collection. frame_request_callback_collection_.RegisterFrameCallback(requestId, frame_callback); @@ -59,20 +69,8 @@ uint32_t ScriptAnimationController::RegisterFrameCallback(const std::shared_ptr< void ScriptAnimationController::CancelFrameCallback(ExecutingContext* context, uint32_t callback_id, ExceptionState& exception_state) { - if (context->dartMethodPtr()->cancelAnimationFrame == nullptr) { - exception_state.ThrowException( - context->ctx(), ErrorType::InternalError, - "Failed to execute 'cancelAnimationFrame': dart method (cancelAnimationFrame) is not registered."); - return; - } - - context->dartMethodPtr()->cancelAnimationFrame(context->contextId(), callback_id); - auto frame_callback = frame_request_callback_collection_.GetFrameCallback(callback_id); if (frame_callback != nullptr) { - if (frame_callback->status() != FrameCallback::FrameStatus::kExecuting) { - frame_request_callback_collection_.RemoveFrameCallback(callback_id); - } frame_callback->SetStatus(FrameCallback::kCanceled); } } diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 4e8f2159cc..ee7a897c7f 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -23,17 +23,19 @@ namespace webf { static std::atomic context_unique_id{0}; #define MAX_JS_CONTEXT 8192 -bool valid_contexts[MAX_JS_CONTEXT]; +thread_local std::unordered_map valid_contexts; std::atomic running_context_list{0}; ExecutingContext::ExecutingContext(DartIsolateContext* dart_isolate_context, - int32_t contextId, + bool is_dedicated, + double context_id, JSExceptionHandler handler, void* owner) : dart_isolate_context_(dart_isolate_context), - context_id_(contextId), + context_id_(context_id), handler_(std::move(handler)), owner_(owner), + is_dedicated_(is_dedicated), unique_id_(context_unique_id++), is_context_valid_(true) { // #if ENABLE_PROFILE @@ -47,10 +49,10 @@ ExecutingContext::ExecutingContext(DartIsolateContext* dart_isolate_context, // #endif // @FIXME: maybe contextId will larger than MAX_JS_CONTEXT - assert_m(valid_contexts[contextId] != true, "Conflict context found!"); - valid_contexts[contextId] = true; - if (contextId > running_context_list) - running_context_list = contextId; + assert_m(valid_contexts[context_id] != true, "Conflict context found!"); + valid_contexts[context_id] = true; + if (context_id > running_context_list) + running_context_list = context_id; time_origin_ = std::chrono::system_clock::now(); @@ -104,7 +106,7 @@ ExecutingContext::~ExecutingContext() { if (JS_IsObject(exception) || JS_IsException(exception)) { // There must be bugs in native functions from call stack frame. Someone needs to fix it if throws. ReportError(exception); - assert_m(false, "Unhandled exception found when Dispose JSContext."); + assert_m(false, "Unhandled exception found when Dispe JSContext."); } JS_FreeValue(script_state_.ctx(), global_object_); @@ -186,6 +188,10 @@ bool ExecutingContext::IsContextValid() const { return is_context_valid_; } +void ExecutingContext::SetContextInValid() { + is_context_valid_ = false; +} + bool ExecutingContext::IsCtxValid() const { return script_state_.Invalid(); } @@ -248,16 +254,14 @@ void ExecutingContext::ReportError(JSValueConst error) { uint32_t messageLength = strlen(type) + strlen(title); if (stack != nullptr) { messageLength += 4 + strlen(stack); - char* message = new char[messageLength]; + char* message = (char*)dart_malloc(messageLength * sizeof(char)); snprintf(message, messageLength, "%s: %s\n%s", type, title, stack); handler_(this, message); - delete[] message; } else { messageLength += 3; - char* message = new char[messageLength]; + char* message = (char*)dart_malloc(messageLength * sizeof(char)); snprintf(message, messageLength, "%s: %s", type, title); handler_(this, message); - delete[] message; } JS_FreeValue(ctx, errorTypeValue); @@ -269,6 +273,7 @@ void ExecutingContext::ReportError(JSValueConst error) { } void ExecutingContext::DrainMicrotasks() { + ui_command_buffer_.addCommand(UICommand::kFinishRecordingCommand, nullptr, nullptr, nullptr); DrainPendingPromiseJobs(); } @@ -370,9 +375,9 @@ static void DispatchPromiseRejectionEvent(const AtomicString& event_type, } } -void ExecutingContext::FlushUICommand() { +void ExecutingContext::FlushUICommand(const BindingObject* self, uint32_t reason) { if (!uiCommandBuffer()->empty()) { - dartMethodPtr()->flushUICommand(context_id_); + dartMethodPtr()->flushUICommand(is_dedicated_, context_id_, self->bindingObject(), reason); } } @@ -496,9 +501,11 @@ void ExecutingContext::InActiveScriptWrappers(ScriptWrappable* script_wrappable) } // A lock free context validator. -bool isContextValid(int32_t contextId) { +bool isContextValid(double contextId) { if (contextId > running_context_list) return false; + if (valid_contexts.count(contextId) == 0) + return false; return valid_contexts[contextId]; } diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index a5a1d9b361..bdd31a0b84 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -30,6 +30,8 @@ #include "frame/module_listener_container.h" #include "script_state.h" +#include "shared_ui_command.h" + namespace webf { struct NativeByteCode { @@ -45,12 +47,13 @@ class MemberMutationScope; class ErrorEvent; class DartContext; class MutationObserver; +class BindingObject; class ScriptWrappable; using JSExceptionHandler = std::function; using MicrotaskCallback = void (*)(void* data); -bool isContextValid(int32_t contextId); +bool isContextValid(double contextId); // An environment in which script can execute. This class exposes the common // properties of script execution environments on the webf. @@ -59,7 +62,8 @@ class ExecutingContext { public: ExecutingContext() = delete; ExecutingContext(DartIsolateContext* dart_isolate_context, - int32_t contextId, + bool is_dedicated, + double context_id, JSExceptionHandler handler, void* owner); ~ExecutingContext(); @@ -76,10 +80,11 @@ class ExecutingContext { bool EvaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine); bool EvaluateByteCode(uint8_t* bytes, size_t byteLength); bool IsContextValid() const; + void SetContextInValid(); bool IsCtxValid() const; JSValue Global(); JSContext* ctx(); - FORCE_INLINE int32_t contextId() const { return context_id_; }; + FORCE_INLINE double contextId() const { return context_id_; }; FORCE_INLINE int32_t uniqueId() const { return unique_id_; } void* owner(); bool HandleException(JSValue* exc); @@ -123,15 +128,16 @@ class ExecutingContext { FORCE_INLINE Window* window() const { return window_; } FORCE_INLINE DartIsolateContext* dartIsolateContext() const { return dart_isolate_context_; }; FORCE_INLINE Performance* performance() const { return performance_; } - FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &ui_command_buffer_; }; - FORCE_INLINE const std::unique_ptr& dartMethodPtr() { + FORCE_INLINE SharedUICommand* uiCommandBuffer() { return &ui_command_buffer_; }; + FORCE_INLINE DartMethodPointer* dartMethodPtr() const { assert(dart_isolate_context_->valid()); return dart_isolate_context_->dartMethodPtr(); } + FORCE_INLINE bool isDedicated() { return is_dedicated_; } FORCE_INLINE std::chrono::time_point timeOrigin() const { return time_origin_; } // Force dart side to execute the pending ui commands. - void FlushUICommand(); + void FlushUICommand(const BindingObject* self, uint32_t reason); void TurnOnJavaScriptGC(); void TurnOffJavaScriptGC(); @@ -171,7 +177,7 @@ class ExecutingContext { // Members first initialized and destructed at the last. // Keep uiCommandBuffer below dartMethod ptr to make sure we can flush all disposeEventTarget when UICommandBuffer // release. - UICommandBuffer ui_command_buffer_{this}; + SharedUICommand ui_command_buffer_{this}; DartIsolateContext* dart_isolate_context_{nullptr}; // Keep uiCommandBuffer above ScriptState to make sure we can collect all disposedEventTarget command when free // JSContext. When call JSFreeContext(ctx) inside ScriptState, all eventTargets will be finalized and UICommandBuffer @@ -183,8 +189,8 @@ class ExecutingContext { // ---------------------------------------------------------------------- // All members below will be free before ScriptState freed. // ---------------------------------------------------------------------- - bool is_context_valid_{false}; - int32_t context_id_; + std::atomic is_context_valid_{false}; + double context_id_; JSExceptionHandler handler_; void* owner_; JSValue global_object_{JS_NULL}; @@ -199,6 +205,7 @@ class ExecutingContext { RejectedPromises rejected_promises_; MemberMutationScope* active_mutation_scope{nullptr}; std::set active_wrappers_; + bool is_dedicated_; }; class ObjectProperty { diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 23a2763e82..7cd739d080 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -13,19 +13,19 @@ using namespace webf; TEST(Context, isValid) { { auto env = TEST_init(); - EXPECT_EQ(env->page()->GetExecutingContext()->IsContextValid(), true); - EXPECT_EQ(env->page()->GetExecutingContext()->IsCtxValid(), true); + EXPECT_EQ(env->page()->executingContext()->IsContextValid(), true); + EXPECT_EQ(env->page()->executingContext()->IsCtxValid(), true); } { auto env = TEST_init(); - EXPECT_EQ(env->page()->GetExecutingContext()->IsContextValid(), true); - EXPECT_EQ(env->page()->GetExecutingContext()->IsCtxValid(), true); + EXPECT_EQ(env->page()->executingContext()->IsContextValid(), true); + EXPECT_EQ(env->page()->executingContext()->IsCtxValid(), true); } } TEST(Context, evalWithError) { static bool errorHandlerExecuted = false; - auto errorHandler = [](int32_t contextId, const char* errmsg) { + auto errorHandler = [](double contextId, const char* errmsg) { errorHandlerExecuted = true; EXPECT_STREQ(errmsg, "TypeError: cannot read property 'toString' of null\n" @@ -39,7 +39,7 @@ TEST(Context, evalWithError) { TEST(Context, recursionThrowError) { static bool errorHandlerExecuted = false; - auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; + auto errorHandler = [](double contextId, const char* errmsg) { errorHandlerExecuted = true; }; auto env = TEST_init(errorHandler); const char* code = "addEventListener('click', (evt) => {\n" @@ -53,7 +53,7 @@ TEST(Context, recursionThrowError) { TEST(Context, unrejectPromiseError) { static bool errorHandlerExecuted = false; - auto errorHandler = [](int32_t contextId, const char* errmsg) { + auto errorHandler = [](double contextId, const char* errmsg) { errorHandlerExecuted = true; EXPECT_STREQ(errmsg, "TypeError: cannot read property 'forceNullError' of null\n" @@ -76,7 +76,7 @@ TEST(Context, unrejectPromiseError) { TEST(Context, globalErrorHandlerTargetReturnToWindow) { static bool logCalled = false; - auto errorHandler = [](int32_t contextId, const char* errmsg) {}; + auto errorHandler = [](double contextId, const char* errmsg) {}; auto env = TEST_init(errorHandler); webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; @@ -98,7 +98,7 @@ throw oldError; TEST(Context, unrejectPromiseWillTriggerUnhandledRejectionEvent) { static bool errorHandlerExecuted = false; static bool logCalled = false; - auto errorHandler = [](int32_t contextId, const char* errmsg) { + auto errorHandler = [](double contextId, const char* errmsg) { errorHandlerExecuted = true; EXPECT_STREQ(errmsg, "TypeError: cannot read property 'forceNullError' of null\n" @@ -139,7 +139,7 @@ var p = new Promise(function (resolve, reject) { TEST(Context, handledRejectionWillNotTriggerUnHandledRejectionEvent) { static bool errorHandlerExecuted = false; static bool logCalled = false; - auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; + auto errorHandler = [](double contextId, const char* errmsg) { errorHandlerExecuted = true; }; auto env = TEST_init(errorHandler); webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; @@ -174,7 +174,7 @@ generateRejectedPromise(true); TEST(Context, unhandledRejectionEventWillTriggerWhenNotHandled) { static bool logCalled = false; - auto errorHandler = [](int32_t contextId, const char* errmsg) {}; + auto errorHandler = [](double contextId, const char* errmsg) {}; auto env = TEST_init(errorHandler); webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; @@ -205,7 +205,7 @@ generateRejectedPromise(true); TEST(Context, handledRejectionEventWillTriggerWhenUnHandledRejectHandled) { static bool errorHandlerExecuted = false; static bool logCalled = false; - auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; + auto errorHandler = [](double contextId, const char* errmsg) { errorHandlerExecuted = true; }; auto env = TEST_init(errorHandler); webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; @@ -237,7 +237,7 @@ generateRejectedPromise(); )"; env->page()->evaluateScript(code.c_str(), code.size(), "file://", 0); - TEST_runLoop(env->page()->GetExecutingContext()); + TEST_runLoop(env->page()->executingContext()); EXPECT_EQ(errorHandlerExecuted, false); EXPECT_EQ(logCalled, true); webf::WebFPage::consoleMessageHandler = nullptr; @@ -246,7 +246,7 @@ generateRejectedPromise(); TEST(Context, unrejectPromiseErrorWithMultipleContext) { static bool errorHandlerExecuted = false; static int32_t errorCalledCount = 0; - auto errorHandler = [](int32_t contextId, const char* errmsg) { + auto errorHandler = [](double contextId, const char* errmsg) { errorHandlerExecuted = true; errorCalledCount++; EXPECT_STREQ(errmsg, @@ -274,12 +274,12 @@ TEST(Context, unrejectPromiseErrorWithMultipleContext) { TEST(Context, disposeContext) { auto mockedDartMethods = TEST_getMockDartMethods(nullptr); - void* dart_context = initDartIsolateContext(mockedDartMethods.data(), mockedDartMethods.size()); - uint32_t contextId = 0; - auto* page = reinterpret_cast(allocateNewPage(dart_context, contextId)); + void* dart_context = initDartIsolateContextSync(0, mockedDartMethods.data(), mockedDartMethods.size()); + double contextId = 0; + auto* page = reinterpret_cast(allocateNewPageSync(0.0, dart_context)); static bool disposed = false; page->disposeCallback = [](webf::WebFPage* bridge) { disposed = true; }; - disposePage(dart_context, page); + disposePageSync(false, dart_context, page); EXPECT_EQ(disposed, true); } @@ -291,7 +291,7 @@ TEST(Context, window) { EXPECT_STREQ(message.c_str(), "true"); }; - auto errorHandler = [](int32_t contextId, const char* errmsg) { + auto errorHandler = [](double contextId, const char* errmsg) { errorHandlerExecuted = true; WEBF_LOG(VERBOSE) << errmsg; }; @@ -310,7 +310,7 @@ TEST(Context, windowInheritEventTarget) { EXPECT_STREQ(message.c_str(), "Æ’ () Æ’ () Æ’ () true"); }; - auto errorHandler = [](int32_t contextId, const char* errmsg) { + auto errorHandler = [](double contextId, const char* errmsg) { errorHandlerExecuted = true; WEBF_LOG(VERBOSE) << errmsg; }; @@ -331,7 +331,7 @@ TEST(Context, evaluateByteCode) { EXPECT_STREQ(message.c_str(), "Arguments {0: 1, 1: 2, 2: 3, 3: 4, callee: Æ’ (), length: 4}"); }; - auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; + auto errorHandler = [](double contextId, const char* errmsg) { errorHandlerExecuted = true; }; auto env = TEST_init(errorHandler); const char* code = "function f() { console.log(arguments)} f(1,2,3,4);"; size_t byteLen; @@ -343,14 +343,14 @@ TEST(Context, evaluateByteCode) { } TEST(jsValueToNativeString, utf8String) { - auto env = TEST_init([](int32_t contextId, const char* errmsg) {}); - JSValue str = JS_NewString(env->page()->GetExecutingContext()->ctx(), "helloworld"); + auto env = TEST_init([](double contextId, const char* errmsg) {}); + JSValue str = JS_NewString(env->page()->executingContext()->ctx(), "helloworld"); std::unique_ptr nativeString = - webf::jsValueToNativeString(env->page()->GetExecutingContext()->ctx(), str); + webf::jsValueToNativeString(env->page()->executingContext()->ctx(), str); EXPECT_EQ(nativeString->length(), 10); uint8_t expectedString[10] = {104, 101, 108, 108, 111, 119, 111, 114, 108, 100}; for (int i = 0; i < 10; i++) { EXPECT_EQ(expectedString[i], *(nativeString->string() + i)); } - JS_FreeValue(env->page()->GetExecutingContext()->ctx(), str); + JS_FreeValue(env->page()->executingContext()->ctx(), str); } diff --git a/bridge/core/frame/console_test.cc b/bridge/core/frame/console_test.cc index b7a1198802..e05f8cd00f 100644 --- a/bridge/core/frame/console_test.cc +++ b/bridge/core/frame/console_test.cc @@ -27,7 +27,7 @@ TEST(Console, log) { WEBF_LOG(VERBOSE) << message; logExecuted = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; exit(1); }); diff --git a/bridge/core/frame/dom_timer_test.cc b/bridge/core/frame/dom_timer_test.cc index 37578aa4e4..5fbaad13dc 100644 --- a/bridge/core/frame/dom_timer_test.cc +++ b/bridge/core/frame/dom_timer_test.cc @@ -37,7 +37,7 @@ console.log('1234'); )"; env->page()->evaluateScript(code.c_str(), code.size(), "vm://", 0); - TEST_runLoop(env->page()->GetExecutingContext()); + TEST_runLoop(env->page()->executingContext()); } TEST(Timer, clearTimeout) { @@ -65,7 +65,7 @@ clearTimeout(timer); )"; env->page()->evaluateScript(code.c_str(), code.size(), "vm://", 0); - TEST_runLoop(env->page()->GetExecutingContext()); + TEST_runLoop(env->page()->executingContext()); EXPECT_EQ(log_called, false); } @@ -82,5 +82,5 @@ let timer = setTimeout(() => { )"; env->page()->evaluateScript(code.c_str(), code.size(), "vm://", 0); - TEST_runLoop(env->page()->GetExecutingContext()); + TEST_runLoop(env->page()->executingContext()); } diff --git a/bridge/core/frame/legacy/location.cc b/bridge/core/frame/legacy/location.cc index 6cbb58900a..f6cc9e0593 100644 --- a/bridge/core/frame/legacy/location.cc +++ b/bridge/core/frame/legacy/location.cc @@ -4,18 +4,13 @@ */ #include "location.h" #include "core/executing_context.h" +#include "core/frame/window.h" namespace webf { void Location::__webf_location_reload__(ExecutingContext* context, ExceptionState& exception_state) { - if (context->dartMethodPtr()->reloadApp == nullptr) { - exception_state.ThrowException(context->ctx(), ErrorType::InternalError, - "Failed to execute 'reload': dart method (reloadApp) is not registered."); - return; - } - - context->FlushUICommand(); - context->dartMethodPtr()->reloadApp(context->contextId()); + context->FlushUICommand(context->window(), FlushUICommandReason::kDependentsOnElement); + context->dartMethodPtr()->reloadApp(context->isDedicated(), context->contextId()); } } // namespace webf diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 89bbac5543..42b58f023f 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -5,19 +5,14 @@ #include "module_manager.h" #include "core/executing_context.h" #include "foundation/logging.h" +#include "foundation/native_value.h" +#include "include/dart_api.h" #include "module_callback.h" namespace webf { -struct ModuleContext { - ModuleContext(ExecutingContext* context, const std::shared_ptr& callback) - : context(context), callback(callback) {} - ExecutingContext* context; - std::shared_ptr callback; -}; - NativeValue* handleInvokeModuleTransientCallback(void* ptr, - int32_t contextId, + double contextId, const char* errmsg, NativeValue* extra_data) { auto* moduleContext = static_cast(ptr); @@ -70,10 +65,45 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, return return_value; } +static void ReturnResultToDart(Dart_PersistentHandle persistent_handle, + NativeValue* result, + InvokeModuleResultCallback result_callback) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(persistent_handle); + result_callback(handle, result); + Dart_DeletePersistentHandle_DL(persistent_handle); +} + +static NativeValue* handleInvokeModuleTransientCallbackWrapper(void* ptr, + double context_id, + const char* errmsg, + NativeValue* extra_data, + Dart_Handle dart_handle, + InvokeModuleResultCallback result_callback) { + auto* moduleContext = static_cast(ptr); + +#if FLUTTER_BACKEND + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + moduleContext->context->dartIsolateContext()->dispatcher()->PostToJs( + moduleContext->context->isDedicated(), moduleContext->context->contextId(), + [](ModuleContext* module_context, double context_id, const char* errmsg, NativeValue* extra_data, + Dart_PersistentHandle persistent_handle, InvokeModuleResultCallback result_callback) { + NativeValue* result = handleInvokeModuleTransientCallback(module_context, context_id, errmsg, extra_data); + module_context->context->dartIsolateContext()->dispatcher()->PostToDart( + module_context->context->isDedicated(), ReturnResultToDart, persistent_handle, result, result_callback); + }, + moduleContext, context_id, errmsg, extra_data, persistent_handle, result_callback); + return nullptr; +#else + return handleInvokeModuleTransientCallback(moduleContext, context_id, errmsg, extra_data); +#endif +} + NativeValue* handleInvokeModuleUnexpectedCallback(void* callbackContext, - int32_t contextId, + double contextId, const char* errmsg, - NativeValue* extra_data) { + NativeValue* extra_data, + Dart_Handle dart_handle, + InvokeModuleResultCallback result_callback) { static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); return nullptr; } @@ -106,25 +136,21 @@ ScriptValue ModuleManager::__webf_invoke_module__(ExecutingContext* context, return ScriptValue::Empty(context->ctx()); } - if (context->dartMethodPtr()->invokeModule == nullptr) { - exception.ThrowException( - context->ctx(), ErrorType::InternalError, - "Failed to execute '__webf_invoke_module__': dart method (invokeModule) is not registered."); - return ScriptValue::Empty(context->ctx()); - } - NativeValue* result; + auto module_name_string = module_name.ToNativeString(context->ctx()); + auto method_name_string = method.ToNativeString(context->ctx()); + if (callback != nullptr) { auto module_callback = ModuleCallback::Create(callback); auto module_context = std::make_shared(context, module_callback); context->ModuleContexts()->AddModuleContext(module_context); - result = context->dartMethodPtr()->invokeModule( - module_context.get(), context->contextId(), module_name.ToNativeString(context->ctx()).release(), - method.ToNativeString(context->ctx()).release(), ¶ms, handleInvokeModuleTransientCallback); + result = context->dartMethodPtr()->invokeModule(context->isDedicated(), module_context.get(), context->contextId(), + module_name_string.get(), method_name_string.get(), ¶ms, + handleInvokeModuleTransientCallbackWrapper); } else { - result = context->dartMethodPtr()->invokeModule( - nullptr, context->contextId(), module_name.ToNativeString(context->ctx()).release(), - method.ToNativeString(context->ctx()).release(), ¶ms, handleInvokeModuleUnexpectedCallback); + result = context->dartMethodPtr()->invokeModule(context->isDedicated(), nullptr, context->contextId(), + module_name_string.get(), method_name_string.get(), ¶ms, + handleInvokeModuleUnexpectedCallback); } if (result == nullptr) { @@ -132,7 +158,7 @@ ScriptValue ModuleManager::__webf_invoke_module__(ExecutingContext* context, } ScriptValue return_value = ScriptValue(context->ctx(), *result); - delete result; + dart_free(result); return return_value; } diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index d69f302882..229fd2f348 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -12,6 +12,13 @@ namespace webf { +struct ModuleContext { + ModuleContext(ExecutingContext* context, const std::shared_ptr& callback) + : context(context), callback(callback) {} + ExecutingContext* context; + std::shared_ptr callback; +}; + class ModuleManager { public: static ScriptValue __webf_invoke_module__(ExecutingContext* context, diff --git a/bridge/core/frame/module_manager_test.cc b/bridge/core/frame/module_manager_test.cc index 64a2d9d6aa..54b8976bd3 100644 --- a/bridge/core/frame/module_manager_test.cc +++ b/bridge/core/frame/module_manager_test.cc @@ -10,10 +10,10 @@ namespace webf { TEST(ModuleManager, ShouldReturnCorrectValue) { bool static errorCalled = false; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto env = TEST_init([](double contextId, const char* errmsg) { errorCalled = true; }); webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); std::string code = std::string(R"( let object = { @@ -35,14 +35,14 @@ console.log(result); TEST(ModuleManager, shouldThrowErrorWhenBadJSON) { bool static errorCalled = false; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { std::string stdErrorMsg = std::string(errmsg); EXPECT_EQ(stdErrorMsg.find("TypeError: circular reference") != std::string::npos, true); errorCalled = true; }); webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); std::string code = std::string(R"( let object = { @@ -64,7 +64,7 @@ webf.methodChannel.invokeMethod('abc', 'fn', object); TEST(ModuleManager, invokeModuleError) { bool static logCalled = false; - auto env = TEST_init([](int32_t contextId, const char* errmsg) {}); + auto env = TEST_init([](double contextId, const char* errmsg) {}); webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ( @@ -75,7 +75,7 @@ TEST(ModuleManager, invokeModuleError) { "'}"); }; - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); std::string code = std::string(R"( function f() { diff --git a/bridge/core/frame/screen.cc b/bridge/core/frame/screen.cc index e02ee7f93e..0a79217a50 100644 --- a/bridge/core/frame/screen.cc +++ b/bridge/core/frame/screen.cc @@ -10,6 +10,7 @@ namespace webf { Screen::Screen(Window* window, NativeBindingObject* native_binding_object) - : EventTargetWithInlineData(window->GetExecutingContext(), native_binding_object) {} + : EventTargetWithInlineData(window->GetExecutingContext(), native_binding_object), + extra_(static_cast(native_binding_object->extra)) {} } // namespace webf diff --git a/bridge/core/frame/screen.d.ts b/bridge/core/frame/screen.d.ts index bdf763dba5..3bf4dfce37 100644 --- a/bridge/core/frame/screen.d.ts +++ b/bridge/core/frame/screen.d.ts @@ -1,10 +1,10 @@ import {EventTarget} from "../dom/events/event_target"; export interface Screen extends EventTarget { - readonly availWidth: DartImpl; - readonly availHeight: DartImpl; - readonly width: DartImpl; - readonly height: DartImpl; + readonly availWidth: int64; + readonly availHeight: int64; + readonly width: int64; + readonly height: int64; new(): void; } diff --git a/bridge/core/frame/screen.h b/bridge/core/frame/screen.h index 569058d4a9..0de31133f4 100644 --- a/bridge/core/frame/screen.h +++ b/bridge/core/frame/screen.h @@ -11,7 +11,12 @@ namespace webf { class Window; -struct NativeScreen {}; +struct ScreenData { + int64_t availWidth; + int64_t availHeight; + int64_t width; + int64_t height; +}; class Screen : public EventTargetWithInlineData { DEFINE_WRAPPERTYPEINFO(); @@ -20,7 +25,13 @@ class Screen : public EventTargetWithInlineData { using ImplType = Screen*; explicit Screen(Window* window, NativeBindingObject* binding_object); + [[nodiscard]] int64_t availWidth() const { return extra_->availWidth; } + [[nodiscard]] int64_t availHeight() const { return extra_->availHeight; } + [[nodiscard]] int64_t width() const { return extra_->width; } + [[nodiscard]] int64_t height() const { return extra_->height; } + private: + ScreenData* extra_; }; } // namespace webf diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index 526fb88d38..0e2b5a2e0f 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -109,13 +109,16 @@ Window* Window::open(const AtomicString& url, ExceptionState& exception_state) { const NativeValue args[] = { NativeValueConverter::ToNativeValue(ctx(), url), }; - InvokeBindingMethod(binding_call_methods::kopen, 1, args, exception_state); + InvokeBindingMethod(binding_call_methods::kopen, 1, args, FlushUICommandReason::kDependentsOnElement, + exception_state); return this; } Screen* Window::screen() { if (screen_ == nullptr) { - NativeValue value = GetBindingProperty(binding_call_methods::kscreen, ASSERT_NO_EXCEPTION()); + NativeValue value = GetBindingProperty( + binding_call_methods::kscreen, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, ASSERT_NO_EXCEPTION()); screen_ = MakeGarbageCollected( this, NativeValueConverter>::FromNativeValue(value)); } @@ -131,7 +134,9 @@ void Window::scroll(double x, double y, ExceptionState& exception_state) { NativeValueConverter::ToNativeValue(x), NativeValueConverter::ToNativeValue(y), }; - InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); + InvokeBindingMethod(binding_call_methods::kscroll, 2, args, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, + exception_state); } void Window::scroll(const std::shared_ptr& options, ExceptionState& exception_state) { @@ -139,7 +144,9 @@ void Window::scroll(const std::shared_ptr& options, ExceptionSt NativeValueConverter::ToNativeValue(options->hasLeft() ? options->left() : 0.0), NativeValueConverter::ToNativeValue(options->hasTop() ? options->top() : 0.0), }; - InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); + InvokeBindingMethod(binding_call_methods::kscroll, 2, args, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, + exception_state); } void Window::scrollBy(ExceptionState& exception_state) { @@ -151,7 +158,9 @@ void Window::scrollBy(double x, double y, ExceptionState& exception_state) { NativeValueConverter::ToNativeValue(x), NativeValueConverter::ToNativeValue(y), }; - InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, exception_state); + InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, + exception_state); } void Window::scrollBy(const std::shared_ptr& options, ExceptionState& exception_state) { @@ -159,7 +168,9 @@ void Window::scrollBy(const std::shared_ptr& options, Exception NativeValueConverter::ToNativeValue(options->hasLeft() ? options->left() : 0.0), NativeValueConverter::ToNativeValue(options->hasTop() ? options->top() : 0.0), }; - InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, exception_state); + InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, + exception_state); } void Window::scrollTo(ExceptionState& exception_state) { @@ -195,7 +206,9 @@ void Window::postMessage(const ScriptValue& message, ComputedCssStyleDeclaration* Window::getComputedStyle(Element* element, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter>::ToNativeValue(element)}; - NativeValue result = InvokeBindingMethod(binding_call_methods::kgetComputedStyle, 1, arguments, exception_state); + NativeValue result = InvokeBindingMethod( + binding_call_methods::kgetComputedStyle, 1, arguments, + FlushUICommandReason::kDependentsOnElement | FlushUICommandReason::kDependentsOnLayout, exception_state); return MakeGarbageCollected( GetExecutingContext(), NativeValueConverter>::FromNativeValue(result)); } @@ -207,14 +220,7 @@ ComputedCssStyleDeclaration* Window::getComputedStyle(Element* element, } double Window::requestAnimationFrame(const std::shared_ptr& callback, ExceptionState& exceptionState) { - if (GetExecutingContext()->dartMethodPtr()->flushUICommand == nullptr) { - exceptionState.ThrowException(ctx(), ErrorType::InternalError, - "Failed to execute 'flushUICommand': dart method (flushUICommand) executed " - "with unexpected error."); - return 0; - } - - GetExecutingContext()->FlushUICommand(); + GetExecutingContext()->FlushUICommand(this, FlushUICommandReason::kStandard); auto frame_callback = FrameCallback::Create(GetExecutingContext(), callback); uint32_t request_id = GetExecutingContext()->document()->RequestAnimationFrame(frame_callback, exceptionState); // `-1` represents some error occurred. diff --git a/bridge/core/frame/window.d.ts b/bridge/core/frame/window.d.ts index c0ceca3c3c..ae68f788e5 100644 --- a/bridge/core/frame/window.d.ts +++ b/bridge/core/frame/window.d.ts @@ -32,14 +32,14 @@ interface Window extends EventTarget, WindowEventHandlers, GlobalEventHandlers { readonly self: Window; readonly screen: Screen; - readonly scrollX: DartImpl; - readonly scrollY: DartImpl; - readonly pageXOffset: DartImpl; - readonly pageYOffset: DartImpl; - readonly devicePixelRatio: DartImpl; + readonly scrollX: DartImpl>; + readonly scrollY: DartImpl>; + readonly pageXOffset: DartImpl>; + readonly pageYOffset: DartImpl>; + readonly devicePixelRatio: DartImpl>; readonly colorScheme: DartImpl; - readonly innerWidth: DartImpl; - readonly innerHeight: DartImpl; + readonly innerWidth: DartImpl>; + readonly innerHeight: DartImpl>; new(): void; } diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 1fe01361d2..f52191772d 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -7,13 +7,14 @@ namespace webf { -static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { +static void handleTimerCallback(DOMTimer* timer, char* errmsg) { auto* context = timer->context(); if (errmsg != nullptr) { JSValue exception = JS_ThrowTypeError(context->ctx(), "%s", errmsg); context->HandleException(&exception); context->Timers()->forceStopTimeoutById(timer->timerId()); + dart_free(errmsg); return; } @@ -24,7 +25,7 @@ static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { timer->Fire(); } -static void handleTransientCallback(void* ptr, int32_t contextId, const char* errmsg) { +static void handleTransientCallback(void* ptr, double contextId, char* errmsg) { if (!isContextValid(contextId)) return; @@ -45,7 +46,7 @@ static void handleTransientCallback(void* ptr, int32_t contextId, const char* er context->Timers()->removeTimeoutById(timer->timerId()); } -static void handlePersistentCallback(void* ptr, int32_t contextId, const char* errmsg) { +static void handlePersistentCallback(void* ptr, double contextId, char* errmsg) { if (!isContextValid(contextId)) return; @@ -75,6 +76,28 @@ static void handlePersistentCallback(void* ptr, int32_t contextId, const char* e timer->SetStatus(DOMTimer::TimerStatus::kFinished); } +static void handleTransientCallbackWrapper(void* ptr, double contextId, char* errmsg) { + auto* timer = static_cast(ptr); + auto* context = timer->context(); + + if (!context->IsContextValid()) + return; + + context->dartIsolateContext()->dispatcher()->PostToJs(context->isDedicated(), contextId, + webf::handleTransientCallback, ptr, contextId, errmsg); +} + +static void handlePersistentCallbackWrapper(void* ptr, double contextId, char* errmsg) { + auto* timer = static_cast(ptr); + auto* context = timer->context(); + + if (!context->IsContextValid()) + return; + + context->dartIsolateContext()->dispatcher()->PostToJs(context->isDedicated(), contextId, + webf::handlePersistentCallback, ptr, contextId, errmsg); +} + int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, std::shared_ptr handler, ExceptionState& exception) { @@ -85,25 +108,17 @@ int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, std::shared_ptr handler, int32_t timeout, ExceptionState& exception) { -#if FLUTTER_BACKEND - if (context->dartMethodPtr()->setTimeout == nullptr) { - exception.ThrowException(context->ctx(), ErrorType::InternalError, - "Failed to execute 'setTimeout': dart method (setTimeout) is not registered."); - return -1; - } -#endif - // Create a timer object to keep track timer callback. auto timer = DOMTimer::create(context, handler, DOMTimer::TimerKind::kOnce); - auto timerId = - context->dartMethodPtr()->setTimeout(timer.get(), context->contextId(), handleTransientCallback, timeout); + auto timer_id = context->dartMethodPtr()->setTimeout(context->isDedicated(), timer.get(), context->contextId(), + handleTransientCallbackWrapper, timeout); // Register timerId. - timer->setTimerId(timerId); + timer->setTimerId(timer_id); - context->Timers()->installNewTimer(context, timerId, timer); + context->Timers()->installNewTimer(context, timer_id, timer); - return timerId; + return timer_id; } int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, @@ -116,17 +131,11 @@ int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, std::shared_ptr handler, int32_t timeout, ExceptionState& exception) { - if (context->dartMethodPtr()->setInterval == nullptr) { - exception.ThrowException(context->ctx(), ErrorType::InternalError, - "Failed to execute 'setInterval': dart method (setInterval) is not registered."); - return -1; - } - // Create a timer object to keep track timer callback. auto timer = DOMTimer::create(context, handler, DOMTimer::TimerKind::kMultiple); - int32_t timerId = - context->dartMethodPtr()->setInterval(timer.get(), context->contextId(), handlePersistentCallback, timeout); + int32_t timerId = context->dartMethodPtr()->setInterval(context->isDedicated(), timer.get(), context->contextId(), + handlePersistentCallbackWrapper, timeout); // Register timerId. timer->setTimerId(timerId); @@ -136,24 +145,12 @@ int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, } void WindowOrWorkerGlobalScope::clearTimeout(ExecutingContext* context, int32_t timerId, ExceptionState& exception) { - if (context->dartMethodPtr()->clearTimeout == nullptr) { - exception.ThrowException(context->ctx(), ErrorType::InternalError, - "Failed to execute 'clearTimeout': dart method (clearTimeout) is not registered."); - return; - } - - context->dartMethodPtr()->clearTimeout(context->contextId(), timerId); + context->dartMethodPtr()->clearTimeout(context->isDedicated(), context->contextId(), timerId); context->Timers()->forceStopTimeoutById(timerId); } void WindowOrWorkerGlobalScope::clearInterval(ExecutingContext* context, int32_t timerId, ExceptionState& exception) { - if (context->dartMethodPtr()->clearTimeout == nullptr) { - exception.ThrowException(context->ctx(), ErrorType::InternalError, - "Failed to execute 'clearTimeout': dart method (clearTimeout) is not registered."); - return; - } - - context->dartMethodPtr()->clearTimeout(context->contextId(), timerId); + context->dartMethodPtr()->clearTimeout(context->isDedicated(), context->contextId(), timerId); context->Timers()->forceStopTimeoutById(timerId); } diff --git a/bridge/core/frame/window_test.cc b/bridge/core/frame/window_test.cc index fad4dab7de..6c6d35a645 100644 --- a/bridge/core/frame/window_test.cc +++ b/bridge/core/frame/window_test.cc @@ -16,11 +16,11 @@ TEST(Window, windowIsGlobalThis) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "console.log(window === globalThis)"; env->page()->evaluateScript(code, strlen(code), "vm://", 0); @@ -35,11 +35,11 @@ TEST(Window, instanceofEventTarget) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "console.log(window instanceof EventTarget)"; env->page()->evaluateScript(code, strlen(code), "vm://", 0); @@ -63,7 +63,7 @@ requestAnimationFrame(() => { )"; env->page()->evaluateScript(code.c_str(), code.size(), "vm://", 0); - TEST_runLoop(env->page()->GetExecutingContext()); + TEST_runLoop(env->page()->executingContext()); EXPECT_EQ(logCalled, true); } @@ -81,7 +81,7 @@ TEST(Window, cancelAnimationFrame) { )"; env->page()->evaluateScript(code.c_str(), code.size(), "vm://", 0); - TEST_runLoop(env->page()->GetExecutingContext()); + TEST_runLoop(env->page()->executingContext()); } TEST(Window, postMessage) { @@ -125,7 +125,7 @@ TEST(Window, location) { TEST(Window, onloadShouldExist) { static bool errorCalled = false; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -140,7 +140,7 @@ TEST(Window, onloadShouldExist) { TEST(Window, atob) { static bool errorCalled = false; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); @@ -156,7 +156,7 @@ console.log(a.charCodeAt(1)) TEST(Window, btoaToEmpty) { static bool errorCalled = false; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); diff --git a/bridge/core/geometry/dom_matrix_readonly.cc b/bridge/core/geometry/dom_matrix_readonly.cc index 34713e60b5..53e8f388a2 100644 --- a/bridge/core/geometry/dom_matrix_readonly.cc +++ b/bridge/core/geometry/dom_matrix_readonly.cc @@ -17,16 +17,15 @@ DOMMatrixReadonly::DOMMatrixReadonly(ExecutingContext* context, const std::shared_ptr& init, ExceptionState& exception_state) : BindingObject(context->ctx()) { - assert(GetExecutingContext()->dartMethodPtr()->create_binding_object != nullptr); - NativeValue arguments[1]; if (init->IsDomString()) { arguments[0] = NativeValueConverter::ToNativeValue(ctx(), init->GetAsDomString()); } else if (init->IsSequenceDouble()) { arguments[0] = NativeValueConverter>::ToNativeValue(init->GetAsSequenceDouble()); } - GetExecutingContext()->dartMethodPtr()->create_binding_object( - GetExecutingContext()->contextId(), bindingObject(), CreateBindingObjectType::kCreateDOMMatrix, arguments, 1); + GetExecutingContext()->dartMethodPtr()->createBindingObject(GetExecutingContext()->isDedicated(), + GetExecutingContext()->contextId(), bindingObject(), + CreateBindingObjectType::kCreateDOMMatrix, arguments, 1); } NativeValue DOMMatrixReadonly::HandleCallFromDartSide(const AtomicString& method, diff --git a/bridge/core/html/canvas/canvas_pattern.cc b/bridge/core/html/canvas/canvas_pattern.cc index be261fbe55..85c143c4c8 100644 --- a/bridge/core/html/canvas/canvas_pattern.cc +++ b/bridge/core/html/canvas/canvas_pattern.cc @@ -13,7 +13,8 @@ CanvasPattern::CanvasPattern(ExecutingContext* context, NativeBindingObject* nat void CanvasPattern::setTransform(DOMMatrix* dom_matrix, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter>::ToNativeValue(dom_matrix)}; - InvokeBindingMethod(binding_call_methods::ksetTransform, 1, arguments, exception_state); + InvokeBindingMethod(binding_call_methods::ksetTransform, 1, arguments, FlushUICommandReason::kDependentsOnElement, + exception_state); } NativeValue CanvasPattern::HandleCallFromDartSide(const AtomicString& method, diff --git a/bridge/core/html/canvas/canvas_rendering_context_2d.cc b/bridge/core/html/canvas/canvas_rendering_context_2d.cc index 0c557736dc..669d15afd7 100644 --- a/bridge/core/html/canvas/canvas_rendering_context_2d.cc +++ b/bridge/core/html/canvas/canvas_rendering_context_2d.cc @@ -35,8 +35,9 @@ CanvasGradient* CanvasRenderingContext2D::createLinearGradient(double x0, NativeValueConverter::ToNativeValue(y0), NativeValueConverter::ToNativeValue(x1), NativeValueConverter::ToNativeValue(y1)}; - NativeValue value = InvokeBindingMethod(binding_call_methods::kcreateLinearGradient, - sizeof(arguments) / sizeof(NativeValue), arguments, exception_state); + NativeValue value = + InvokeBindingMethod(binding_call_methods::kcreateLinearGradient, sizeof(arguments) / sizeof(NativeValue), + arguments, FlushUICommandReason::kDependentsOnElement, exception_state); NativeBindingObject* native_binding_object = NativeValueConverter>::FromNativeValue(value); return MakeGarbageCollected(GetExecutingContext(), native_binding_object); @@ -57,8 +58,9 @@ CanvasGradient* CanvasRenderingContext2D::createRadialGradient(double x0, NativeValueConverter::ToNativeValue(y1), NativeValueConverter::ToNativeValue(r1), }; - NativeValue value = InvokeBindingMethod(binding_call_methods::kcreateRadialGradient, - sizeof(arguments) / sizeof(NativeValue), arguments, exception_state); + NativeValue value = + InvokeBindingMethod(binding_call_methods::kcreateRadialGradient, sizeof(arguments) / sizeof(NativeValue), + arguments, FlushUICommandReason::kDependentsOnElement, exception_state); NativeBindingObject* native_binding_object = NativeValueConverter>::FromNativeValue(value); return MakeGarbageCollected(GetExecutingContext(), native_binding_object); @@ -80,7 +82,7 @@ CanvasPattern* CanvasRenderingContext2D::createPattern( arguments[1] = NativeValueConverter::ToNativeValue(ctx(), repetition); NativeValue value = InvokeBindingMethod(binding_call_methods::kcreatePattern, sizeof(arguments) / sizeof(NativeValue), - arguments, exception_state); + arguments, FlushUICommandReason::kDependentsOnElement, exception_state); NativeBindingObject* native_binding_object = NativeValueConverter>::FromNativeValue(value); return MakeGarbageCollected(GetExecutingContext(), native_binding_object); diff --git a/bridge/core/html/canvas/html_canvas_element.cc b/bridge/core/html/canvas/html_canvas_element.cc index 4feb3a5793..0f5e03c40f 100644 --- a/bridge/core/html/canvas/html_canvas_element.cc +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -16,7 +16,8 @@ HTMLCanvasElement::HTMLCanvasElement(Document& document) : HTMLElement(html_name CanvasRenderingContext* HTMLCanvasElement::getContext(const AtomicString& type, ExceptionState& exception_state) { NativeValue arguments[] = {NativeValueConverter::ToNativeValue(ctx(), type)}; - NativeValue value = InvokeBindingMethod(binding_call_methods::kgetContext, 1, arguments, exception_state); + NativeValue value = InvokeBindingMethod(binding_call_methods::kgetContext, 1, arguments, + FlushUICommandReason::kDependentsOnElement, exception_state); NativeBindingObject* native_binding_object = NativeValueConverter>::FromNativeValue(value); diff --git a/bridge/core/html/custom/widget_element.cc b/bridge/core/html/custom/widget_element.cc index 958b479b39..751ca84d0c 100644 --- a/bridge/core/html/custom/widget_element.cc +++ b/bridge/core/html/custom/widget_element.cc @@ -28,7 +28,7 @@ bool WidgetElement::IsValidName(const AtomicString& name) { } bool WidgetElement::NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state) { - return GetExecutingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(key); + return GetExecutingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(key.ToStdString(ctx())); } void WidgetElement::NamedPropertyEnumerator(std::vector& names, ExceptionState& exception_state) { @@ -42,39 +42,44 @@ void WidgetElement::NamedPropertyEnumerator(std::vector& names, Ex } } -NativeValue WidgetElement::HandleCallFromDartSide(const AtomicString& method, - int32_t argc, - const NativeValue* argv, - Dart_Handle dart_object) { - MemberMutationScope mutation_scope{GetExecutingContext()}; - - if (method == binding_call_methods::ksyncPropertiesAndMethods) { - return HandleSyncPropertiesAndMethodsFromDart(argc, argv); - } - - return Element::HandleCallFromDartSide(method, argc, argv, dart_object); -} - ScriptValue WidgetElement::item(const AtomicString& key, ExceptionState& exception_state) { if (unimplemented_properties_.count(key) > 0) { return unimplemented_properties_[key]; } - if (!GetExecutingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(tagName())) { - GetExecutingContext()->FlushUICommand(); + std::string shape_key = tagName().ToStdString(ctx()); + std::string property_key = key.ToStdString(ctx()); + bool have_shape = true; + + if (!GetExecutingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(shape_key)) { + GetExecutingContext()->FlushUICommand(this, FlushUICommandReason::kDependentsOnElement); + have_shape = false; } if (key == built_in_string::kSymbol_toStringTag) { return ScriptValue(ctx(), tagName().ToNativeString(ctx()).release()); } - auto shape = GetExecutingContext()->dartIsolateContext()->EnsureData()->GetWidgetElementShape(tagName()); + const WidgetElementShape* shape = nullptr; + + if (have_shape) { + shape = GetExecutingContext()->dartIsolateContext()->EnsureData()->GetWidgetElementShape(shape_key); + } else { + NativeValue raw_shapes[3]; + bool is_success = GetExecutingContext()->dartMethodPtr()->getWidgetElementShape( + GetExecutingContext()->isDedicated(), contextId(), bindingObject(), + reinterpret_cast(&raw_shapes)); + if (is_success) { + shape = SaveWidgetElementsShapeData(raw_shapes); + } + } + if (shape != nullptr) { - if (shape->built_in_properties_.find(key) != shape->built_in_properties_.end()) { - return ScriptValue(ctx(), GetBindingProperty(key, exception_state)); + if (shape->built_in_properties_.find(property_key) != shape->built_in_properties_.end()) { + return ScriptValue(ctx(), GetBindingProperty(key, FlushUICommandReason::kDependentsOnElement, exception_state)); } - if (shape->built_in_methods_.find(key) != shape->built_in_methods_.end()) { + if (shape->built_in_methods_.find(property_key) != shape->built_in_methods_.end()) { if (cached_methods_.count(key) > 0) { return cached_methods_[key]; } @@ -84,7 +89,7 @@ ScriptValue WidgetElement::item(const AtomicString& key, ExceptionState& excepti return func; } - if (shape->built_in_async_methods_.find(key) != shape->built_in_async_methods_.end()) { + if (shape->built_in_async_methods_.find(property_key) != shape->built_in_async_methods_.end()) { if (async_cached_methods_.count(key) > 0) { return async_cached_methods_[key]; } @@ -99,13 +104,14 @@ ScriptValue WidgetElement::item(const AtomicString& key, ExceptionState& excepti } bool WidgetElement::SetItem(const AtomicString& key, const ScriptValue& value, ExceptionState& exception_state) { - if (!GetExecutingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(tagName())) { - GetExecutingContext()->FlushUICommand(); + if (!GetExecutingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(tagName().ToStdString(ctx()))) { + GetExecutingContext()->FlushUICommand(this, FlushUICommandReason::kDependentsOnElement); } - auto shape = GetExecutingContext()->dartIsolateContext()->EnsureData()->GetWidgetElementShape(tagName()); + auto shape = + GetExecutingContext()->dartIsolateContext()->EnsureData()->GetWidgetElementShape(tagName().ToStdString(ctx())); // This property is defined in the Dart side - if (shape != nullptr && shape->built_in_properties_.count(key) > 0) { + if (shape != nullptr && shape->built_in_properties_.count(key.ToStdString(ctx())) > 0) { NativeValue result = SetBindingProperty(key, value.ToNative(ctx(), exception_state), exception_state); return NativeValueConverter::FromNativeValue(result); } @@ -152,10 +158,9 @@ void WidgetElement::CloneNonAttributePropertiesFrom(const Element& other, CloneC } } -NativeValue WidgetElement::HandleSyncPropertiesAndMethodsFromDart(int32_t argc, const NativeValue* argv) { - assert(argc == 3); +const WidgetElementShape* WidgetElement::SaveWidgetElementsShapeData(const NativeValue* argv) { AtomicString key = tagName(); - assert(!GetExecutingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(key)); + assert(!GetExecutingContext()->dartIsolateContext()->EnsureData()->HasWidgetElementShape(key.ToStdString(ctx()))); auto shape = std::make_shared(); @@ -164,20 +169,20 @@ NativeValue WidgetElement::HandleSyncPropertiesAndMethodsFromDart(int32_t argc, auto&& async_methods = NativeValueConverter>::FromNativeValue(ctx(), argv[2]); for (auto& property : properties) { - shape->built_in_properties_.emplace(property); + shape->built_in_properties_.emplace(property.ToStdString(ctx())); } for (auto& method : sync_methods) { - shape->built_in_methods_.emplace(method); + shape->built_in_methods_.emplace(method.ToStdString(ctx())); } for (auto& method : async_methods) { - shape->built_in_async_methods_.emplace(method); + shape->built_in_async_methods_.emplace(method.ToStdString(ctx())); } - GetExecutingContext()->dartIsolateContext()->EnsureData()->SetWidgetElementShape(key, shape); + GetExecutingContext()->dartIsolateContext()->EnsureData()->SetWidgetElementShape(key.ToStdString(ctx()), shape); - return Native_NewBool(true); + return shape.get(); } ScriptValue WidgetElement::CreateSyncMethodFunc(const AtomicString& method_name) { diff --git a/bridge/core/html/custom/widget_element.h b/bridge/core/html/custom/widget_element.h index d3ce1bba01..a66842caef 100644 --- a/bridge/core/html/custom/widget_element.h +++ b/bridge/core/html/custom/widget_element.h @@ -27,11 +27,6 @@ class WidgetElement : public HTMLElement { bool NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state); void NamedPropertyEnumerator(std::vector& names, ExceptionState&); - NativeValue HandleCallFromDartSide(const AtomicString& method, - int32_t argc, - const NativeValue* argv, - Dart_Handle dart_object) override; - ScriptValue item(const AtomicString& key, ExceptionState& exception_state); bool SetItem(const AtomicString& key, const ScriptValue& value, ExceptionState& exception_state); bool DeleteItem(const AtomicString& key, ExceptionState& exception_state); @@ -45,7 +40,7 @@ class WidgetElement : public HTMLElement { private: ScriptValue CreateSyncMethodFunc(const AtomicString& method_name); ScriptValue CreateAsyncMethodFunc(const AtomicString& method_name); - NativeValue HandleSyncPropertiesAndMethodsFromDart(int32_t argc, const NativeValue* argv); + const WidgetElementShape* SaveWidgetElementsShapeData(const NativeValue* argv); std::unordered_map cached_methods_; std::unordered_map async_cached_methods_; std::unordered_map unimplemented_properties_; diff --git a/bridge/core/html/custom/widget_element_test.cc b/bridge/core/html/custom/widget_element_test.cc index 0d220d0641..e4c3de4e07 100644 --- a/bridge/core/html/custom/widget_element_test.cc +++ b/bridge/core/html/custom/widget_element_test.cc @@ -15,11 +15,11 @@ TEST(WidgetElement, setPropertyEventHandler) { EXPECT_STREQ(message.c_str(), "1111"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let checkbox = document.createElement('flutter-checkbox'); " "function f(){ console.log(1111); }; " @@ -37,11 +37,11 @@ TEST(WidgetElement, getPropertyWithSymbolToStringTag) { EXPECT_STREQ(message.c_str(), "FLUTTER-CHECKBOX"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let checkbox = document.createElement('flutter-checkbox'); " "console.log(checkbox[Symbol.toStringTag])"; diff --git a/bridge/core/html/html_collection_test.cc b/bridge/core/html/html_collection_test.cc index 77178a3c85..97a53d6923 100644 --- a/bridge/core/html/html_collection_test.cc +++ b/bridge/core/html/html_collection_test.cc @@ -14,11 +14,11 @@ TEST(HTMLCollection, children) { EXPECT_STREQ(message.c_str(), "2

"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div');" "let text = document.createTextNode('1234');" @@ -35,11 +35,11 @@ TEST(HTMLCollection, children) { TEST(HTMLCollection, childrenWillNotChangeWithNoElementsNodes) { bool static errorCalled = false; bool static logCalled = false; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('span');" "let text = document.createTextNode('1234');" diff --git a/bridge/core/html/html_element.d.ts b/bridge/core/html/html_element.d.ts index 3e4ba38de0..3b422c7202 100644 --- a/bridge/core/html/html_element.d.ts +++ b/bridge/core/html/html_element.d.ts @@ -4,10 +4,10 @@ import {GlobalEventHandlers} from "../dom/global_event_handlers"; export interface HTMLElement extends Element, GlobalEventHandlers { // CSSOM View Module // https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface - readonly offsetTop: DartImpl; - readonly offsetLeft: DartImpl; - readonly offsetWidth: DartImpl; - readonly offsetHeight: DartImpl; + readonly offsetTop: DartImpl>; + readonly offsetLeft: DartImpl>; + readonly offsetWidth: DartImpl>; + readonly offsetHeight: DartImpl>; click(): DartImpl; diff --git a/bridge/core/html/html_element_test.cc b/bridge/core/html/html_element_test.cc index 734c20e22b..147ad126ef 100644 --- a/bridge/core/html/html_element_test.cc +++ b/bridge/core/html/html_element_test.cc @@ -15,11 +15,11 @@ TEST(HTMLElement, globalEventHandlerRegistered) { EXPECT_STREQ(message.c_str(), "1234"); logCalled = true; }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let div = document.createElement('div'); function f(){ console.log(1234); }; div.onclick = f; " "div.dispatchEvent(new Event('click'));"; diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index f1d9852c51..6d2825476e 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -22,7 +22,8 @@ ScriptPromise HTMLImageElement::decode(ExceptionState& exception_state) const { AtomicString HTMLImageElement::src() const { ExceptionState exception_state; - NativeValue native_value = GetBindingProperty(binding_call_methods::ksrc, exception_state); + NativeValue native_value = + GetBindingProperty(binding_call_methods::ksrc, FlushUICommandReason::kDependentsOnElement, exception_state); typename NativeTypeString::ImplType v = NativeValueConverter::FromNativeValue(ctx(), std::move(native_value)); if (UNLIKELY(exception_state.HasException())) { diff --git a/bridge/core/html/html_image_element.d.ts b/bridge/core/html/html_image_element.d.ts index cb246cbb9a..85401ed9b7 100644 --- a/bridge/core/html/html_image_element.d.ts +++ b/bridge/core/html/html_image_element.d.ts @@ -6,10 +6,10 @@ interface HTMLImageElement extends HTMLElement { src: string; // srcset: DartImpl; sizes: DartImpl; - width: DartImpl; - height: DartImpl; - readonly naturalWidth: DartImpl; - readonly naturalHeight: DartImpl; + width: DartImpl>; + height: DartImpl>; + readonly naturalWidth: DartImpl>; + readonly naturalHeight: DartImpl>; readonly complete: DartImpl; readonly currentSrc: DartImpl; decoding: DartImpl; diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 25066f862d..55f043aac1 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -22,13 +22,16 @@ namespace webf { ConsoleMessageHandler WebFPage::consoleMessageHandler{nullptr}; -WebFPage::WebFPage(DartIsolateContext* dart_isolate_context, int32_t contextId, const JSExceptionHandler& handler) - : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { +WebFPage::WebFPage(DartIsolateContext* dart_isolate_context, + bool is_dedicated, + double context_id, + const JSExceptionHandler& handler) + : ownerThreadId(std::this_thread::get_id()), dart_isolate_context_(dart_isolate_context) { context_ = new ExecutingContext( - dart_isolate_context, contextId, + dart_isolate_context, is_dedicated, context_id, [](ExecutingContext* context, const char* message) { - if (context->IsContextValid() && context->dartMethodPtr()->onJsError != nullptr) { - context->dartMethodPtr()->onJsError(context->contextId(), message); + if (context->IsContextValid()) { + context->dartMethodPtr()->onJSError(context->isDedicated(), context->contextId(), message); } WEBF_LOG(ERROR) << message << std::endl; }, @@ -39,14 +42,18 @@ bool WebFPage::parseHTML(const char* code, size_t length) { if (!context_->IsContextValid()) return false; - MemberMutationScope scope{context_}; + { + MemberMutationScope scope{context_}; - auto document_element = context_->document()->documentElement(); - if (!document_element) { - return false; + auto document_element = context_->document()->documentElement(); + if (!document_element) { + return false; + } + + HTMLParser::parseHTML(code, length, context_->document()->documentElement()); } - HTMLParser::parseHTML(code, length, context_->document()->documentElement()); + context_->uiCommandBuffer()->addCommand(UICommand::kFinishRecordingCommand, nullptr, nullptr, nullptr); return true; } diff --git a/bridge/core/page.h b/bridge/core/page.h index 2ee3aab7e3..a8d60f4b66 100644 --- a/bridge/core/page.h +++ b/bridge/core/page.h @@ -32,7 +32,10 @@ class WebFPage final { public: static ConsoleMessageHandler consoleMessageHandler; WebFPage() = delete; - WebFPage(DartIsolateContext* dart_isolate_context, int32_t jsContext, const JSExceptionHandler& handler); + WebFPage(DartIsolateContext* dart_isolate_context, + bool is_dedicated, + double context_id, + const JSExceptionHandler& handler); ~WebFPage(); // Bytecodes which registered by webf plugins. @@ -52,7 +55,8 @@ class WebFPage final { std::thread::id currentThread() const; - [[nodiscard]] ExecutingContext* GetExecutingContext() const { return context_; } + [[nodiscard]] ExecutingContext* executingContext() const { return context_; } + [[nodiscard]] DartIsolateContext* dartIsolateContext() const { return dart_isolate_context_; } NativeValue* invokeModuleEvent(SharedNativeString* moduleName, const char* eventType, @@ -60,7 +64,9 @@ class WebFPage final { NativeValue* extra); void reportError(const char* errmsg); - int32_t contextId; + FORCE_INLINE bool isDedicated() { return context_->isDedicated(); }; + FORCE_INLINE double contextId() { return context_->contextId(); } + #if IS_TEST // the owner pointer which take JSBridge as property. void* owner; @@ -71,6 +77,7 @@ class WebFPage final { // FIXME: we must to use raw pointer instead of unique_ptr because we needs to access context_ when dispose page. // TODO: Raw pointer is dangerous and just works but it's fragile. We needs refactor this for more stable and // maintainable. + DartIsolateContext* dart_isolate_context_; ExecutingContext* context_; JSExceptionHandler handler_; }; diff --git a/bridge/core/script_state.h b/bridge/core/script_state.h index 8cb58170c2..5762f6d76e 100644 --- a/bridge/core/script_state.h +++ b/bridge/core/script_state.h @@ -24,7 +24,7 @@ class ScriptState { inline bool Invalid() const { return !ctx_invalid_; } inline JSContext* ctx() { - assert(!ctx_invalid_ && "GetExecutingContext has been released"); + assert(!ctx_invalid_ && "executingContext has been released"); return ctx_; } JSRuntime* runtime(); diff --git a/bridge/core/timing/performance.cc b/bridge/core/timing/performance.cc index 54699266a0..bb3555c9a6 100644 --- a/bridge/core/timing/performance.cc +++ b/bridge/core/timing/performance.cc @@ -111,7 +111,7 @@ void Performance::clearMarks(ExceptionState& exception_state) { } it++; } - entries_ = new_entries; + std::swap(entries_, new_entries); } void Performance::clearMarks(const AtomicString& name, ExceptionState& exception_state) { @@ -126,7 +126,7 @@ void Performance::clearMarks(const AtomicString& name, ExceptionState& exception it++; } - entries_ = new_entries; + std::swap(entries_, new_entries); } void Performance::clearMeasures(ExceptionState& exception_state) { @@ -140,7 +140,7 @@ void Performance::clearMeasures(ExceptionState& exception_state) { it++; } - entries_ = new_entries; + std::swap(entries_, new_entries); } void Performance::clearMeasures(const AtomicString& name, ExceptionState& exception_state) { @@ -154,7 +154,7 @@ void Performance::clearMeasures(const AtomicString& name, ExceptionState& except it++; } - entries_ = new_entries; + std::swap(entries_, new_entries); } void Performance::Trace(GCVisitor* visitor) const { @@ -282,566 +282,4 @@ void Performance::measure(const AtomicString& measure_name, } } -// JSValue Performance::clearMarks(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// JSValue targetMark = JS_NULL; -// if (argc == 1) { -// targetMark = argv[0]; -// } -// -// auto* entries = performance->m_nativePerformance.entries; -// auto it = std::begin(*entries); -// -// while (it != entries->end()) { -// char* entryType = (*it)->entryType; -// if (strcmp(entryType, "mark") == 0) { -// if (JS_IsNull(targetMark)) { -// entries->erase(it); -// } else { -// std::string entryName = (*it)->name; -// std::string targetName = jsValueToStdString(ctx, targetMark); -// if (entryName == targetName) { -// entries->erase(it); -// } else { -// it++; -// }; -// } -// } else { -// it++; -// } -// } -// -// return JS_NULL; -//} -// JSValue Performance::clearMeasures(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// JSValue targetMark = JS_NULL; -// if (argc == 1) { -// targetMark = argv[0]; -// } -// -// auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// auto entries = performance->m_nativePerformance.entries; -// auto it = std::begin(*entries); -// -// while (it != entries->end()) { -// char* entryType = (*it)->entryType; -// if (strcmp(entryType, "measure") == 0) { -// if (JS_IsNull(targetMark)) { -// entries->erase(it); -// } else { -// std::string entryName = (*it)->name; -// std::string targetName = jsValueToStdString(ctx, targetMark); -// if (entryName == targetName) { -// entries->erase(it); -// } else { -// it++; -// } -// } -// } else { -// it++; -// } -// } -// -// return JS_NULL; -//} -// JSValue Performance::getEntries(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// auto entries = performance->getFullEntries(); -// -// size_t entriesSize = entries.size(); -// JSValue returnArray = JS_NewArray(ctx); -// JSValue pushMethod = JS_GetPropertyStr(ctx, returnArray, "push"); -// -// for (size_t i = 0; i < entriesSize; i++) { -// auto& entry = entries[i]; -// auto entryType = std::string(entry->entryType); -// JSValue v = buildPerformanceEntry(entryType, performance->m_context, entry); -// JS_Call(ctx, pushMethod, returnArray, 1, &v); -// JS_FreeValue(ctx, v); -// } -// -// JS_FreeValue(ctx, pushMethod); -// return returnArray; -//} -// JSValue Performance::getEntriesByName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// if (argc == 0) { -// return JS_ThrowTypeError( -// ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); -// } -// -// std::string targetName = jsValueToStdString(ctx, argv[0]); -// auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// auto entries = performance->getFullEntries(); -// JSValue targetEntriesArray = JS_NewArray(ctx); -// JSValue pushMethod = JS_GetPropertyStr(ctx, targetEntriesArray, "push"); -// -// for (auto& m_entries : entries) { -// if (m_entries->name == targetName) { -// std::string entryType = std::string(m_entries->entryType); -// JSValue entry = buildPerformanceEntry(entryType, performance->m_context, m_entries); -// JS_Call(ctx, pushMethod, targetEntriesArray, 1, &entry); -// } -// } -// -// JS_FreeValue(ctx, pushMethod); -// return targetEntriesArray; -//} -// JSValue Performance::getEntriesByType(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// if (argc == 0) { -// return JS_ThrowTypeError( -// ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); -// } -// -// std::string entryType = jsValueToStdString(ctx, argv[0]); -// auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// auto entries = performance->getFullEntries(); -// JSValue targetEntriesArray = JS_NewArray(ctx); -// JSValue pushMethod = JS_GetPropertyStr(ctx, targetEntriesArray, "push"); -// -// for (auto& m_entries : entries) { -// if (m_entries->entryType == entryType) { -// JSValue entry = buildPerformanceEntry(entryType, performance->m_context, m_entries); -// JS_Call(ctx, pushMethod, targetEntriesArray, 1, &entry); -// } -// } -// -// JS_FreeValue(ctx, pushMethod); -// return targetEntriesArray; -//} -// JSValue Performance::mark(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// if (argc != 1) { -// return JS_ThrowTypeError(ctx, -// "Failed to execute 'mark' on 'Performance': 1 argument required, but only 0 present."); -// } -// -// auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// std::string markName = jsValueToStdString(ctx, argv[0]); -// performance->m_nativePerformance.mark(markName); -// -// return JS_NULL; -//} -// JSValue Performance::measure(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// if (argc == 0) { -// return JS_ThrowTypeError(ctx, -// "Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 -// present."); -// } -// -// std::string name = jsValueToStdString(ctx, argv[0]); -// std::string startMark; -// std::string endMark; -// -// if (argc > 1) { -// if (!JS_IsUndefined(argv[1])) { -// startMark = jsValueToStdString(ctx, argv[1]); -// } -// } -// -// if (argc > 2) { -// endMark = jsValueToStdString(ctx, argv[2]); -// } -// -// auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// JSValue exception = JS_NULL; -// performance->internalMeasure(name, startMark, endMark, &exception); -// -// if (!JS_IsNull(exception)) -// return exception; -// -// return JS_NULL; -//} -// -// PerformanceEntry::PerformanceEntry(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) -// : HostObject(context, "PerformanceEntry"), m_nativePerformanceEntry(nativePerformanceEntry) {} -// -// PerformanceMark::PerformanceMark(ExecutionContext* context, std::string& name, int64_t startTime) -// : PerformanceEntry(context, -// new NativePerformanceEntry(name, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} -// PerformanceMark::PerformanceMark(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) -// : PerformanceEntry(context, nativePerformanceEntry) {} -// PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, -// std::string& name, -// int64_t startTime, -// int64_t duration) -// : PerformanceEntry( -// context, -// new NativePerformanceEntry(name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} -// PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) -// : PerformanceEntry(context, nativePerformanceEntry) {} -// void NativePerformance::mark(const std::string& markName) { -// int64_t startTime = std::chrono::duration_cast(system_clock::now().time_since_epoch()).count(); -// auto* nativePerformanceEntry = -// new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; -// entries->emplace_back(nativePerformanceEntry); -//} -// void NativePerformance::mark(const std::string& markName, int64_t startTime) { -// auto* nativePerformanceEntry = -// new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; -// entries->emplace_back(nativePerformanceEntry); -//} -// -// Performance::Performance(ExecutionContext* context) : HostObject(context, "Performance") {} -// void Performance::internalMeasure(const std::string& name, -// const std::string& startMark, -// const std::string& endMark, -// JSValue* exception) { -// auto entries = getFullEntries(); -// -// if (!startMark.empty() && !endMark.empty()) { -// size_t startMarkCount = -// std::count_if(entries.begin(), entries.end(), -// [&startMark](NativePerformanceEntry* entry) -> bool { return entry->name == startMark; }); -// -// if (startMarkCount == 0) { -// *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not -// exist.", -// startMark.c_str()); -// return; -// } -// -// size_t endMarkCount = -// std::count_if(entries.begin(), entries.end(), -// [&endMark](NativePerformanceEntry* entry) -> bool { return entry->name == endMark; }); -// -// if (endMarkCount == 0) { -// *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not -// exist.", -// endMark.c_str()); -// return; -// } -// -// if (startMarkCount != endMarkCount) { -// *exception = JS_ThrowTypeError( -// m_ctx, -// "Failed to execute 'measure' on 'Performance': The mark %s and %s does not appear the same number of times", -// startMark.c_str(), endMark.c_str()); -// return; -// } -// -// auto startIt = std::begin(entries); -// auto endIt = std::begin(entries); -// -// for (size_t i = 0; i < startMarkCount; i++) { -// auto startEntry = std::find_if(startIt, entries.end(), [&startMark](NativePerformanceEntry* entry) -> bool { -// return entry->name == startMark; -// }); -// -// bool isStartEntryHasUniqueId = (*startEntry)->uniqueId != PERFORMANCE_ENTRY_NONE_UNIQUE_ID; -// -// auto endEntryComparator = [&endMark, &startEntry, -// isStartEntryHasUniqueId](NativePerformanceEntry* entry) -> bool { -// if (isStartEntryHasUniqueId) { -// return entry->uniqueId == (*startEntry)->uniqueId && entry->name == endMark; -// } -// return entry->name == endMark; -// }; -// -// auto endEntry = std::find_if(startEntry, entries.end(), endEntryComparator); -// -// if (endEntry == entries.end()) { -// size_t startIndex = startEntry - entries.begin(); -// assert_m(false, ("Can not get endEntry. startIndex: " + std::to_string(startIndex) + -// " startMark: " + startMark + " endMark: " + endMark)); -// } -// -// int64_t duration = (*endEntry)->startTime - (*startEntry)->startTime; -// int64_t startTime = std::chrono::duration_cast(system_clock::now().time_since_epoch()).count(); -// auto* nativePerformanceEntry = -// new NativePerformanceEntry{name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; -// m_nativePerformance.entries->emplace_back(nativePerformanceEntry); -// startIt = ++startEntry; -// endIt = ++endEntry; -// } -// } -//} -// double Performance::now() const { -// auto now = std::chrono::system_clock::now(); -// auto duration = std::chrono::duration_cast(now - GetExecutingContext()->timeOrigin); -// auto reducedDuration = std::floor(duration / 1000us) * 1000us; -// return std::chrono::duration_cast(reducedDuration).count(); -//} -// std::vector Performance::getFullEntries() { -// auto* bridgeEntries = m_nativePerformance.entries; -//#if ENABLE_PROFILE -// if (getDartMethod()->getPerformanceEntries == nullptr) { -// return std::vector(); -// } -// auto dartEntryList = getDartMethod()->getPerformanceEntries(m_context->getContextId()); -// if (dartEntryList == nullptr) -// return std::vector(); -// auto dartEntityBytes = dartEntryList->entries; -// std::vector dartEntries; -// dartEntries.reserve(dartEntryList->length); -// -// for (size_t i = 0; i < dartEntryList->length * 3; i += 3) { -// const char* name = reinterpret_cast(dartEntityBytes[i]); -// int64_t startTime = dartEntityBytes[i + 1]; -// int64_t uniqueId = dartEntityBytes[i + 2]; -// auto* nativePerformanceEntry = new NativePerformanceEntry(name, "mark", startTime, 0, uniqueId); -// dartEntries.emplace_back(nativePerformanceEntry); -// } -//#endif -// -// std::vector mergedEntries; -// -// mergedEntries.insert(mergedEntries.end(), bridgeEntries->begin(), bridgeEntries->end()); -//#if ENABLE_PROFILE -// mergedEntries.insert(mergedEntries.end(), dartEntries.begin(), dartEntries.end()); -// delete[] dartEntryList->entries; -// delete dartEntryList; -//#endif -// -// return mergedEntries; -//} -// -//#if ENABLE_PROFILE -// -// void Performance::measureSummary(JSValue* exception) { -// internalMeasure(PERF_WIDGET_CREATION_COST, PERF_CONTROLLER_INIT_START, PERF_CONTROLLER_INIT_END, exception); -// internalMeasure(PERF_CONTROLLER_PROPERTIES_INIT_COST, PERF_CONTROLLER_INIT_START, PERF_CONTROLLER_PROPERTY_INIT, -// exception); -// internalMeasure(PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST, PERF_VIEW_CONTROLLER_INIT_START, -// PERF_VIEW_CONTROLLER_PROPERTY_INIT, exception); -// internalMeasure(PERF_BRIDGE_INIT_COST, PERF_BRIDGE_INIT_START, PERF_BRIDGE_INIT_END, exception); -// internalMeasure(PERF_BRIDGE_REGISTER_DART_METHOD_COST, PERF_BRIDGE_REGISTER_DART_METHOD_START, -// PERF_BRIDGE_REGISTER_DART_METHOD_END, exception); -// internalMeasure(PERF_CREATE_VIEWPORT_COST, PERF_CREATE_VIEWPORT_START, PERF_CREATE_VIEWPORT_END, exception); -// internalMeasure(PERF_ELEMENT_MANAGER_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, PERF_ELEMENT_MANAGER_INIT_END, -// exception); -// internalMeasure(PERF_ELEMENT_MANAGER_PROPERTIES_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, -// PERF_ELEMENT_MANAGER_PROPERTY_INIT, exception); -// internalMeasure(PERF_ROOT_ELEMENT_INIT_COST, PERF_ROOT_ELEMENT_INIT_START, PERF_ROOT_ELEMENT_INIT_END, exception); -// internalMeasure(PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST, PERF_ROOT_ELEMENT_INIT_START, -// PERF_ROOT_ELEMENT_PROPERTY_INIT, -// exception); -// internalMeasure(PERF_JS_CONTEXT_INIT_COST, PERF_JS_CONTEXT_INIT_START, PERF_JS_CONTEXT_INIT_END, exception); -// internalMeasure(PERF_JS_HOST_CLASS_GET_PROPERTY_COST, PERF_JS_HOST_CLASS_GET_PROPERTY_START, -// PERF_JS_HOST_CLASS_GET_PROPERTY_END, exception); -// internalMeasure(PERF_JS_HOST_CLASS_SET_PROPERTY_COST, PERF_JS_HOST_CLASS_SET_PROPERTY_START, -// PERF_JS_HOST_CLASS_SET_PROPERTY_END, exception); -// internalMeasure(PERF_JS_HOST_CLASS_INIT_COST, PERF_JS_HOST_CLASS_INIT_START, PERF_JS_HOST_CLASS_INIT_END, -// exception); internalMeasure(PERF_JS_NATIVE_FUNCTION_CALL_COST, PERF_JS_NATIVE_FUNCTION_CALL_START, -// PERF_JS_NATIVE_FUNCTION_CALL_END, exception); -// internalMeasure(PERF_JS_NATIVE_METHOD_INIT_COST, PERF_JS_NATIVE_METHOD_INIT_START, PERF_JS_NATIVE_METHOD_INIT_END, -// exception); -// internalMeasure(PERF_JS_POLYFILL_INIT_COST, PERF_JS_POLYFILL_INIT_START, PERF_JS_POLYFILL_INIT_END, exception); -// internalMeasure(PERF_JS_BUNDLE_LOAD_COST, PERF_JS_BUNDLE_LOAD_START, PERF_JS_BUNDLE_LOAD_END, exception); -// internalMeasure(PERF_JS_BUNDLE_EVAL_COST, PERF_JS_BUNDLE_EVAL_START, PERF_JS_BUNDLE_EVAL_END, exception); -// internalMeasure(PERF_FLUSH_UI_COMMAND_COST, PERF_FLUSH_UI_COMMAND_START, PERF_FLUSH_UI_COMMAND_END, exception); -// internalMeasure(PERF_CREATE_ELEMENT_COST, PERF_CREATE_ELEMENT_START, PERF_CREATE_ELEMENT_END, exception); -// internalMeasure(PERF_CREATE_TEXT_NODE_COST, PERF_CREATE_TEXT_NODE_START, PERF_CREATE_TEXT_NODE_END, exception); -// internalMeasure(PERF_CREATE_COMMENT_COST, PERF_CREATE_COMMENT_START, PERF_CREATE_COMMENT_END, exception); -// internalMeasure(PERF_DISPOSE_EVENT_TARGET_COST, PERF_DISPOSE_EVENT_TARGET_START, PERF_DISPOSE_EVENT_TARGET_END, -// exception); -// internalMeasure(PERF_ADD_EVENT_COST, PERF_ADD_EVENT_START, PERF_ADD_EVENT_END, exception); -// internalMeasure(PERF_INSERT_ADJACENT_NODE_COST, PERF_INSERT_ADJACENT_NODE_START, PERF_INSERT_ADJACENT_NODE_END, -// exception); -// internalMeasure(PERF_REMOVE_NODE_COST, PERF_REMOVE_NODE_START, PERF_REMOVE_NODE_END, exception); -// internalMeasure(PERF_SET_STYLE_COST, PERF_SET_STYLE_START, PERF_SET_STYLE_END, exception); -// internalMeasure(PERF_SET_PROPERTIES_COST, PERF_SET_PROPERTIES_START, PERF_SET_PROPERTIES_END, exception); -// internalMeasure(PERF_REMOVE_PROPERTIES_COST, PERF_REMOVE_PROPERTIES_START, PERF_REMOVE_PROPERTIES_END, exception); -// internalMeasure(PERF_FLEX_LAYOUT_COST, PERF_FLEX_LAYOUT_START, PERF_FLEX_LAYOUT_END, exception); -// internalMeasure(PERF_FLOW_LAYOUT_COST, PERF_FLOW_LAYOUT_START, PERF_FLOW_LAYOUT_END, exception); -// internalMeasure(PERF_INTRINSIC_LAYOUT_COST, PERF_INTRINSIC_LAYOUT_START, PERF_INTRINSIC_LAYOUT_END, exception); -// internalMeasure(PERF_SILVER_LAYOUT_COST, PERF_SILVER_LAYOUT_START, PERF_SILVER_LAYOUT_END, exception); -// internalMeasure(PERF_PAINT_COST, PERF_PAINT_START, PERF_PAINT_END, exception); -// internalMeasure(PERF_DOM_FORCE_LAYOUT_COST, PERF_DOM_FORCE_LAYOUT_START, PERF_DOM_FORCE_LAYOUT_END, exception); -// internalMeasure(PERF_DOM_FLUSH_UI_COMMAND_COST, PERF_DOM_FLUSH_UI_COMMAND_START, PERF_DOM_FLUSH_UI_COMMAND_END, -// exception); -// internalMeasure(PERF_JS_PARSE_TIME_COST, PERF_JS_PARSE_TIME_START, PERF_JS_PARSE_TIME_END, exception); -//} -// -// std::vector findAllMeasures(const std::vector& entries, -// const std::string& targetName) { -// std::vector resultEntries; -// -// for (auto entry : entries) { -// if (entry->name == targetName) { -// resultEntries.emplace_back(entry); -// } -// } -// -// return resultEntries; -//}; -// -// double getMeasureTotalDuration(const std::vector& measures) { -// double duration = 0.0; -// for (auto entry : measures) { -// duration += entry->duration; -// } -// return duration / 1000; -//} - -// JSValue Performance::__webf_navigation_summary__(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// JSValue exception = JS_NULL; -// performance->measureSummary(&exception); -// -// std::vector entries = performance->getFullEntries(); -// -// if (entries.empty()) { -// return JS_ThrowTypeError(ctx, "Failed to get navigation summary: flutter is not running in profile mode."); -// } -// -// std::vector measures; -// for (auto& m_entries : entries) { -// if (std::string(m_entries->entryType) == "measure") { -// measures.emplace_back(m_entries); -// } -// } -// -//#define GET_COST_WITH_DECREASE(NAME, MACRO, DECREASE) \ -// auto NAME##Measures = findAllMeasures(measures, MACRO); \ -// size_t NAME##Count = NAME##Measures.size(); \ -// double NAME##Cost = getMeasureTotalDuration(NAME##Measures) - (DECREASE); \ -// auto NAME##Avg = NAME##Measures.empty() ? 0 : (NAME##Cost) / NAME##Measures.size(); -// -//#define GET_COST(NAME, MACRO) \ -// auto NAME##Measures = findAllMeasures(measures, MACRO); \ -// size_t NAME##Count = NAME##Measures.size(); \ -// double NAME##Cost = getMeasureTotalDuration(NAME##Measures); \ -// auto NAME##Avg = NAME##Measures.empty() ? 0 : NAME##Cost / NAME##Measures.size(); -// -// GET_COST(widgetCreation, PERF_WIDGET_CREATION_COST); -// GET_COST(controllerPropertiesInit, PERF_CONTROLLER_PROPERTIES_INIT_COST); -// GET_COST(viewControllerPropertiesInit, PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST); -// GET_COST(bridgeInit, PERF_BRIDGE_INIT_COST); -// GET_COST(bridgeRegisterDartMethod, PERF_BRIDGE_REGISTER_DART_METHOD_COST); -// GET_COST(createViewport, PERF_CREATE_VIEWPORT_COST); -// GET_COST(elementManagerInit, PERF_ELEMENT_MANAGER_INIT_COST); -// GET_COST(elementManagerPropertiesInit, PERF_ELEMENT_MANAGER_PROPERTIES_INIT_COST); -// GET_COST(rootElementInit, PERF_ROOT_ELEMENT_INIT_COST); -// GET_COST(rootElementPropertiesInit, PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST); -// GET_COST(jsContextInit, PERF_JS_CONTEXT_INIT_COST); -// GET_COST(jsNativeMethodInit, PERF_JS_NATIVE_METHOD_INIT_COST); -// GET_COST(jsPolyfillInit, PERF_JS_POLYFILL_INIT_COST); -// GET_COST(jsBundleLoad, PERF_JS_BUNDLE_LOAD_COST); -// GET_COST(jsParseTime, PERF_JS_PARSE_TIME_COST); -// GET_COST(flushUiCommand, PERF_FLUSH_UI_COMMAND_COST); -// GET_COST(createElement, PERF_CREATE_ELEMENT_COST); -// GET_COST(createTextNode, PERF_CREATE_TEXT_NODE_COST); -// GET_COST(createComment, PERF_CREATE_COMMENT_COST); -// GET_COST(disposeEventTarget, PERF_DISPOSE_EVENT_TARGET_COST); -// GET_COST(addEvent, PERF_ADD_EVENT_COST); -// GET_COST(insertAdjacentNode, PERF_INSERT_ADJACENT_NODE_COST); -// GET_COST(removeNode, PERF_REMOVE_NODE_COST); -// GET_COST(setStyle, PERF_SET_STYLE_COST); -// GET_COST(setProperties, PERF_SET_PROPERTIES_COST); -// GET_COST(removeProperties, PERF_REMOVE_PROPERTIES_COST); -// GET_COST(flexLayout, PERF_FLEX_LAYOUT_COST); -// GET_COST(flowLayout, PERF_FLOW_LAYOUT_COST); -// GET_COST(intrinsicLayout, PERF_INTRINSIC_LAYOUT_COST); -// GET_COST(silverLayout, PERF_SILVER_LAYOUT_COST); -// GET_COST(paint, PERF_PAINT_COST); -// GET_COST(domForceLayout, PERF_DOM_FORCE_LAYOUT_COST); -// GET_COST(domFlushUICommand, PERF_DOM_FLUSH_UI_COMMAND_COST); -// GET_COST_WITH_DECREASE(jsHostClassGetProperty, PERF_JS_HOST_CLASS_GET_PROPERTY_COST, -// domForceLayoutCost + domFlushUICommandCost) -// GET_COST(jsHostClassSetProperty, PERF_JS_HOST_CLASS_SET_PROPERTY_COST); -// GET_COST(jsHostClassInit, PERF_JS_HOST_CLASS_INIT_COST); -// GET_COST(jsNativeFunction, PERF_JS_NATIVE_FUNCTION_CALL_COST); -// GET_COST_WITH_DECREASE(jsBundleEval, PERF_JS_BUNDLE_EVAL_COST, domForceLayoutCost + domFlushUICommandCost); -// -// double initBundleCost = jsBundleLoadCost + jsBundleEvalCost + flushUiCommandCost + createElementCost + -// createTextNodeCost + createCommentCost + disposeEventTargetCost + addEventCost + -// insertAdjacentNodeCost + removeNodeCost + setStyleCost + setPropertiesCost + -// removePropertiesCost; -// // layout and paint measure are not correct. -// double renderingCost = flexLayoutCost + flowLayoutCost + intrinsicLayoutCost + silverLayoutCost + paintCost; -// double totalCost = widgetCreationCost + initBundleCost; -// -// char buffer[5000]; -// // clang-format off -// sprintf(buffer, R"( -// Total time cost(without paint and layout): %.*fms -// -//%s: %.*fms -// + %s %.*fms -// + %s %.*fms -// + %s %.*fms -// + %s %.*fms -// + %s %.*fms -// + %s %.*fms -// + %s %.*fms -// + %s %.*fms -// + %s %.*fms -// + %s %.*fms -// + %s %.*fms -// + %s %.*fms -// First Bundle Load: %.*fms -// + %s %.*fms -// + %s %.*fms -// + %s %.*fms -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// Rendering: %.*fms -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -// + %s %.*fms avg: %.*fms count: %zu -//)", -// 2, totalCost, -// PERF_WIDGET_CREATION_COST, 2, widgetCreationCost, -// PERF_CONTROLLER_PROPERTIES_INIT_COST, 2, controllerPropertiesInitCost, -// PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST, 2, viewControllerPropertiesInitCost, -// PERF_ELEMENT_MANAGER_INIT_COST, 2, elementManagerInitCost, -// PERF_ELEMENT_MANAGER_PROPERTY_INIT, 2, elementManagerPropertiesInitCost, -// PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST, 2, rootElementPropertiesInitCost, -// PERF_ROOT_ELEMENT_INIT_COST, 2, rootElementInitCost, -// PERF_CREATE_VIEWPORT_COST, 2, createViewportCost, -// PERF_BRIDGE_INIT_COST, 2, bridgeInitCost, -// PERF_BRIDGE_REGISTER_DART_METHOD_COST, 2, bridgeRegisterDartMethodCost, -// PERF_JS_CONTEXT_INIT_COST, 2, jsContextInitCost, -// PERF_JS_NATIVE_METHOD_INIT_COST, 2, jsNativeMethodInitCost, -// PERF_JS_POLYFILL_INIT_COST, 2, jsPolyfillInitCost, -// 2, initBundleCost, -// PERF_JS_BUNDLE_LOAD_COST, 2, jsBundleLoadCost, -// PERF_JS_BUNDLE_EVAL_COST, 2, jsBundleEvalCost, -// PERF_JS_PARSE_TIME_COST, 2, jsParseTimeCost, -// PERF_FLUSH_UI_COMMAND_COST, 2, flushUiCommandCost, 2, flushUiCommandAvg, flushUiCommandCount, -// PERF_CREATE_ELEMENT_COST, 2, createElementCost, 2, createElementAvg, createElementCount, -// PERF_JS_HOST_CLASS_GET_PROPERTY_COST, 2, jsHostClassGetPropertyCost, 2, jsHostClassGetPropertyAvg, -// jsHostClassGetPropertyCount, PERF_JS_HOST_CLASS_SET_PROPERTY_COST, 2, jsHostClassSetPropertyCost, 2, -// jsHostClassSetPropertyAvg, jsHostClassSetPropertyCount, PERF_JS_HOST_CLASS_INIT_COST, 2, -// jsHostClassInitCost, 2, jsHostClassInitAvg, jsHostClassInitCount, PERF_JS_NATIVE_FUNCTION_CALL_COST, 2, -// jsNativeFunctionCost, 2, jsNativeFunctionAvg, jsNativeFunctionCount, PERF_CREATE_TEXT_NODE_COST, 2, -// createTextNodeCost, 2, createTextNodeAvg, createTextNodeCount, PERF_CREATE_COMMENT_COST, 2, -// createCommentCost, 2, createCommentAvg, createCommentCount, PERF_DISPOSE_EVENT_TARGET_COST, 2, -// disposeEventTargetCost, 2, disposeEventTargetAvg, disposeEventTargetCount, PERF_ADD_EVENT_COST, 2, -// addEventCost, 2, addEventAvg, addEventCount, PERF_INSERT_ADJACENT_NODE_COST, 2, insertAdjacentNodeCost, 2, -// insertAdjacentNodeAvg, insertAdjacentNodeCount, PERF_REMOVE_NODE_COST, 2, removeNodeCost, 2, removeNodeAvg, -// removeNodeCount, PERF_SET_STYLE_COST, 2, setStyleCost, 2, setStyleAvg, setStyleCount, -// PERF_DOM_FORCE_LAYOUT_COST, 2, domForceLayoutCost, 2, domForceLayoutAvg, domForceLayoutCount, -// PERF_DOM_FLUSH_UI_COMMAND_COST, 2, domFlushUICommandCost, 2, domFlushUICommandAvg, domFlushUICommandCount, -// PERF_SET_PROPERTIES_COST, 2, setPropertiesCost, 2, setPropertiesAvg, setPropertiesCount, -// PERF_REMOVE_PROPERTIES_COST, 2, removePropertiesCost, 2, removePropertiesAvg, removePropertiesCount, -// 2, renderingCost, -// PERF_FLEX_LAYOUT_COST, 2, flexLayoutCost, 2, flexLayoutAvg, flexLayoutCount, -// PERF_FLOW_LAYOUT_COST, 2, flowLayoutCost, 2, flowLayoutAvg, flowLayoutCount, -// PERF_INTRINSIC_LAYOUT_COST, 2, intrinsicLayoutCost, 2, intrinsicLayoutAvg, intrinsicLayoutCount, -// PERF_SILVER_LAYOUT_COST, 2, silverLayoutCost, 2, silverLayoutAvg, silverLayoutCount, -// PERF_PAINT_COST, 2, paintCost, 2, paintAvg, paintCount -// ); -// // clang-format on -// return JS_NewString(ctx, buffer); -//} - -//#endif - } // namespace webf diff --git a/bridge/core/timing/performance_mark.cc b/bridge/core/timing/performance_mark.cc index bde8ab9d99..f26f6e325f 100644 --- a/bridge/core/timing/performance_mark.cc +++ b/bridge/core/timing/performance_mark.cc @@ -21,7 +21,7 @@ PerformanceMark* PerformanceMark::Create(ExecutingContext* context, start = mark_options->startTime(); if (start < 0) { exception_state.ThrowException(context->ctx(), ErrorType::TypeError, - "'" + name.ToStdString(context->ctx()) + "' cannot have a negative start time."); + "'" + name.ToStdString(context->ctx()) + "' cannot have a negative Start time."); return nullptr; } } else { diff --git a/bridge/core/timing/performance_test.cc b/bridge/core/timing/performance_test.cc index e424db32c9..91a5a15470 100644 --- a/bridge/core/timing/performance_test.cc +++ b/bridge/core/timing/performance_test.cc @@ -15,11 +15,11 @@ TEST(Performance, now) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "console.log(performance.now() < 20);"; env->page()->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -33,11 +33,11 @@ TEST(Performance, timeOrigin) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "console.log(typeof performance.timeOrigin === 'number');"; env->page()->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -51,11 +51,11 @@ TEST(Performance, toJSON) { logCalled = true; EXPECT_STREQ(message.c_str(), "true true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "let json = performance.toJSON();" "console.log('now' in json, 'timeOrigin' in json);"; @@ -71,11 +71,11 @@ TEST(Performance, mark) { logCalled = true; EXPECT_STREQ(message.c_str(), "PerformanceMark {detail: null}"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "performance.mark('abc');" "let entries = performance.getEntries();" @@ -92,11 +92,11 @@ TEST(Performance, markWithDetail) { logCalled = true; EXPECT_STREQ(message.c_str(), "PerformanceMark {detail: {...}}"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "performance.mark('abc', { detail: {value: 1}});" "let entries = performance.getEntries();" @@ -113,11 +113,11 @@ TEST(Performance, markWithName) { logCalled = true; EXPECT_STREQ(message.c_str(), "[PerformanceMark {...}, PerformanceMark {...}]"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "performance.mark('abc', { detail: 1});" "performance.mark('efg', { detail: 2});" @@ -136,11 +136,11 @@ TEST(Performance, clearMarks) { logCalled = true; EXPECT_STREQ(message.c_str(), "[]"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "performance.mark('abc', { detail: 1});" "performance.mark('efg', { detail: 2});" @@ -160,11 +160,11 @@ TEST(Performance, clearMarksByName) { logCalled = true; EXPECT_STREQ(message.c_str(), "[PerformanceMark {...}]"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = "performance.mark('abc', { detail: 1});" "performance.mark('efg', { detail: 2});" @@ -184,11 +184,11 @@ TEST(Performance, measure) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; - auto env = TEST_init([](int32_t contextId, const char* errmsg) { + auto env = TEST_init([](double contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); const char* code = R"( performance.mark('A'); diff --git a/bridge/foundation/logging.cc b/bridge/foundation/logging.cc index 0d7a8ac4d1..be809fd7d2 100644 --- a/bridge/foundation/logging.cc +++ b/bridge/foundation/logging.cc @@ -126,9 +126,8 @@ void printLog(ExecutingContext* context, std::stringstream& stream, std::string webf::WebFPage::consoleMessageHandler(ctx, stream.str(), static_cast(_log_level)); } - if (context->dartMethodPtr()->onJsLog != nullptr) { - context->dartMethodPtr()->onJsLog(context->contextId(), static_cast(_log_level), stream.str().c_str()); - } + context->dartMethodPtr()->onJSLog(context->isDedicated(), context->contextId(), static_cast(_log_level), + stream.str().c_str()); } } // namespace webf diff --git a/bridge/foundation/native_type.cc b/bridge/foundation/native_type.cc index 4d2bb8ff50..ef328f8f4c 100644 --- a/bridge/foundation/native_type.cc +++ b/bridge/foundation/native_type.cc @@ -10,7 +10,7 @@ namespace webf { -void* DartReadable::operator new(std::size_t size) { +void* dart_malloc(std::size_t size) { #if WIN32 return CoTaskMemAlloc(size); #else @@ -18,12 +18,20 @@ void* DartReadable::operator new(std::size_t size) { #endif } -void DartReadable::operator delete(void* memory) noexcept { +void dart_free(void* ptr) { #if WIN32 - return CoTaskMemFree(memory); + return CoTaskMemFree(ptr); #else - return free(memory); + return free(ptr); #endif } +void* DartReadable::operator new(std::size_t size) { + return dart_malloc(size); +} + +void DartReadable::operator delete(void* memory) noexcept { + dart_free(memory); +} + } // namespace webf \ No newline at end of file diff --git a/bridge/foundation/native_type.h b/bridge/foundation/native_type.h index f1994eeb6a..a7871a9151 100644 --- a/bridge/foundation/native_type.h +++ b/bridge/foundation/native_type.h @@ -13,6 +13,9 @@ namespace webf { +void* dart_malloc(std::size_t size); +void dart_free(void* ptr); + // Shared C struct which can be read by dart through Dart FFI. struct DartReadable { // Dart FFI use ole32 as it's allocator, we need to override the default allocator to compact with Dart FFI. diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 368e1bb40d..af6432d9c1 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -60,6 +60,10 @@ struct NativeValueConverter : public NativeValueConverterBase : public NativeValueConverterBase : public NativeValueConverterBase< static NativeValue ToNativeValue(ImplType value) { return Native_NewFloat64(value); } static ImplType FromNativeValue(NativeValue value) { + if (value.tag == NativeTag::TAG_NULL) { + return 0.0; + } + assert(value.tag == NativeTag::TAG_FLOAT64); double result; memcpy(&result, reinterpret_cast(&value.u.int64), sizeof(double)); @@ -93,6 +105,10 @@ struct NativeValueConverter : public NativeValueConverterBase(value.u.ptr); return ScriptValue::CreateJsonObject(ctx, str, strlen(str)); @@ -107,10 +123,18 @@ struct NativeValueConverter, std::enable_if_t> { static NativeValue ToNativeValue(T* value) { return Native_NewPtr(JSPointerType::Others, value); } static T* FromNativeValue(NativeValue value) { + if (value.tag == NativeTag::TAG_NULL) { + return nullptr; + } + assert(value.tag == NativeTag::TAG_POINTER); return static_cast(value.u.ptr); } static T* FromNativeValue(JSContext* ctx, NativeValue value) { + if (value.tag == NativeTag::TAG_NULL) { + return nullptr; + } + assert(value.tag == NativeTag::TAG_POINTER); return static_cast(value.u.ptr); } @@ -121,10 +145,18 @@ struct NativeValueConverter, std::enable_if_t> { static NativeValue ToNativeValue(T* value) { return Native_NewPtr(JSPointerType::Others, value); } static T* FromNativeValue(NativeValue value) { + if (value.tag == NativeTag::TAG_NULL) { + return nullptr; + } + assert(value.tag == NativeTag::TAG_POINTER); return static_cast(value.u.ptr); } static T* FromNativeValue(JSContext* ctx, NativeValue value) { + if (value.tag == NativeTag::TAG_NULL) { + return nullptr; + } + assert(value.tag == NativeTag::TAG_POINTER); return static_cast(value.u.ptr); } @@ -186,6 +218,10 @@ struct NativeValueConverter> : public NativeValueConverterBas } static ImplType FromNativeValue(JSContext* ctx, NativeValue native_value) { + if (native_value.tag == NativeTag::TAG_NULL) { + return std::vector(); + } + assert(native_value.tag == NativeTag::TAG_LIST); size_t length = native_value.uint32; auto* arr = static_cast(native_value.u.ptr); diff --git a/bridge/foundation/shared_ui_command.cc b/bridge/foundation/shared_ui_command.cc new file mode 100644 index 0000000000..b9e157a264 --- /dev/null +++ b/bridge/foundation/shared_ui_command.cc @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "shared_ui_command.h" +#include "core/executing_context.h" +#include "foundation/logging.h" +#include "ui_command_buffer.h" + +namespace webf { + +SharedUICommand::SharedUICommand(ExecutingContext* context) + : context_(context), front_buffer_(std::make_unique(context)), is_blocking_writing_(false) {} + +void SharedUICommand::addCommand(UICommand type, + std::unique_ptr&& args_01, + void* nativePtr, + void* nativePtr2, + bool request_ui_update) { + if (!context_->isDedicated()) { + front_buffer_->addCommand(type, std::move(args_01), nativePtr, nativePtr2, request_ui_update); + return; + } + + while (is_blocking_writing_) { + // simply spin wait for the swapBuffers to finish. + } + + front_buffer_->addCommand(type, std::move(args_01), nativePtr, nativePtr2, request_ui_update); +} + +// first called by dart to begin read commands. +void* SharedUICommand::data() { + return front_buffer_->data(); +} + +uint32_t SharedUICommand::kindFlag() { + return front_buffer_->kindFlag(); +} + +// second called by dart to get the size of commands. +int64_t SharedUICommand::size() { + return front_buffer_->size(); +} + +// third called by dart to clear commands. +void SharedUICommand::clear() { + front_buffer_->clear(); +} + +// called by c++ to check if there are commands. +bool SharedUICommand::empty() { + return front_buffer_->empty(); +} + +void SharedUICommand::acquireLocks() { + is_blocking_writing_ = true; +} + +void SharedUICommand::releaseLocks() { + is_blocking_writing_ = false; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/foundation/shared_ui_command.h b/bridge/foundation/shared_ui_command.h new file mode 100644 index 0000000000..6b96fcc629 --- /dev/null +++ b/bridge/foundation/shared_ui_command.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef MULTI_THREADING_DOUBULE_UI_COMMAND_H_ +#define MULTI_THREADING_DOUBULE_UI_COMMAND_H_ + +#include +#include "foundation/native_type.h" +#include "foundation/ui_command_buffer.h" + +namespace webf { + +class SharedUICommand : public DartReadable { + public: + SharedUICommand(ExecutingContext* context); + + void addCommand(UICommand type, + std::unique_ptr&& args_01, + void* nativePtr, + void* nativePtr2, + bool request_ui_update = true); + + void* data(); + uint32_t kindFlag(); + int64_t size(); + bool empty(); + void clear(); + + void acquireLocks(); + void releaseLocks(); + + private: + std::unique_ptr front_buffer_ = nullptr; + std::atomic is_blocking_writing_; + ExecutingContext* context_; +}; + +} // namespace webf + +#endif // MULTI_THREADING_DOUBULE_UI_COMMAND_H_ \ No newline at end of file diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 670fa07eaf..bbcfee33ef 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -11,6 +11,38 @@ namespace webf { +UICommandKind GetKindFromUICommand(UICommand command) { + switch (command) { + case UICommand::kCreateElement: + case UICommand::kCreateTextNode: + case UICommand::kCreateComment: + case UICommand::kCreateDocument: + case UICommand::kCreateWindow: + case UICommand::kRemoveNode: + case UICommand::kCreateDocumentFragment: + case UICommand::kCreateSVGElement: + case UICommand::kCreateElementNS: + case UICommand::kCloneNode: + return UICommandKind::kNodeCreation; + case UICommand::kInsertAdjacentNode: + return UICommandKind::kNodeMutation; + case UICommand::kAddEvent: + case UICommand::kRemoveEvent: + return UICommandKind::kEvent; + case UICommand::kSetStyle: + case UICommand::kClearStyle: + return UICommandKind::kStyleUpdate; + case UICommand::kSetAttribute: + case UICommand::kRemoveAttribute: + return UICommandKind::kAttributeUpdate; + case UICommand::kDisposeBindingObject: + return UICommandKind::kDisposeBindingObject; + case UICommand::kStartRecordingCommand: + case UICommand::kFinishRecordingCommand: + return UICommandKind::kOperation; + } +} + UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context), buffer_((UICommandItem*)malloc(sizeof(UICommandItem) * MAXIMUM_UI_COMMAND_SIZE)) {} @@ -18,15 +50,35 @@ UICommandBuffer::~UICommandBuffer() { free(buffer_); } -void UICommandBuffer::addCommand(UICommand type, +void UICommandBuffer::addCommand(UICommand command, std::unique_ptr&& args_01, void* nativePtr, void* nativePtr2, bool request_ui_update) { - UICommandItem item{static_cast(type), args_01.get(), nativePtr, nativePtr2}; + if (!is_recording_) { + UICommandItem recording_item{static_cast(UICommand::kStartRecordingCommand), nullptr, nullptr, nullptr}; + updateFlags(command); + addCommand(recording_item, false); + is_recording_ = true; + } + + if (command == UICommand::kFinishRecordingCommand) { + if (size_ == 0) + return; + if (buffer_[size_ - 1].type == static_cast(UICommand::kFinishRecordingCommand)) + return; + } + + UICommandItem item{static_cast(command), args_01.get(), nativePtr, nativePtr2}; + updateFlags(command); addCommand(item, request_ui_update); } +void UICommandBuffer::updateFlags(UICommand command) { + UICommandKind type = GetKindFromUICommand(command); + kind_flag = kind_flag | type; +} + void UICommandBuffer::addCommand(const UICommandItem& item, bool request_ui_update) { if (UNLIKELY(!context_->dartIsolateContext()->valid())) { return; @@ -38,9 +90,8 @@ void UICommandBuffer::addCommand(const UICommandItem& item, bool request_ui_upda } #if FLUTTER_BACKEND - if (UNLIKELY(request_ui_update && !update_batched_ && context_->IsContextValid() && - context_->dartMethodPtr()->requestBatchUpdate != nullptr)) { - context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); + if (UNLIKELY(request_ui_update && !update_batched_ && context_->IsContextValid())) { + context_->dartMethodPtr()->requestBatchUpdate(context_->isDedicated(), context_->contextId()); update_batched_ = true; } #endif @@ -53,6 +104,14 @@ UICommandItem* UICommandBuffer::data() { return buffer_; } +uint32_t UICommandBuffer::kindFlag() { + return kind_flag; +} + +bool UICommandBuffer::isRecording() { + return is_recording_; +} + int64_t UICommandBuffer::size() { return size_; } @@ -62,8 +121,9 @@ bool UICommandBuffer::empty() { } void UICommandBuffer::clear() { + memset(buffer_, 0, sizeof(UICommandItem) * size_); size_ = 0; - memset(buffer_, 0, sizeof(buffer_)); + kind_flag = 0; update_batched_ = false; } diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index edcbe14ebc..42f6fdbc54 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -8,13 +8,23 @@ #include #include "bindings/qjs/native_string_utils.h" -#include "native_value.h" namespace webf { class ExecutingContext; +enum UICommandKind : uint32_t { + kNodeCreation = 1, + kNodeMutation = 1 << 2, + kStyleUpdate = 1 << 3, + kEvent = 1 << 4, + kAttributeUpdate = 1 << 5, + kDisposeBindingObject = 1 << 6, + kOperation = 1 << 7 +}; + enum class UICommand { + kStartRecordingCommand, kCreateElement, kCreateTextNode, kCreateComment, @@ -33,6 +43,7 @@ enum class UICommand { kCreateDocumentFragment, kCreateSVGElement, kCreateElementNS, + kFinishRecordingCommand, }; #define MAXIMUM_UI_COMMAND_SIZE 2048 @@ -52,6 +63,8 @@ struct UICommandItem { int64_t nativePtr2{0}; }; +UICommandKind GetKindFromUICommand(UICommand type); + class UICommandBuffer { public: UICommandBuffer() = delete; @@ -63,18 +76,23 @@ class UICommandBuffer { void* nativePtr2, bool request_ui_update = true); UICommandItem* data(); + uint32_t kindFlag(); + bool isRecording(); int64_t size(); bool empty(); void clear(); private: void addCommand(const UICommandItem& item, bool request_ui_update = true); + void updateFlags(UICommand command); ExecutingContext* context_{nullptr}; UICommandItem* buffer_{nullptr}; + uint32_t kind_flag{0}; bool update_batched_{false}; int64_t size_{0}; int64_t max_size_{MAXIMUM_UI_COMMAND_SIZE}; + bool is_recording_{false}; }; } // namespace webf diff --git a/bridge/include/webf_bridge.h b/bridge/include/webf_bridge.h index 66ef72ca6a..1e1f48664a 100644 --- a/bridge/include/webf_bridge.h +++ b/bridge/include/webf_bridge.h @@ -7,6 +7,7 @@ #define WEBF_BRIDGE_EXPORT_H #include +#include #include #if defined(_WIN32) @@ -30,26 +31,54 @@ struct WebFInfo { }; typedef void (*Task)(void*); +typedef std::function DartWork; +typedef void (*AllocateNewPageCallback)(Dart_Handle dart_handle, void*); +typedef void (*DisposePageCallback)(Dart_Handle dart_handle); +typedef void (*InvokeModuleEventCallback)(Dart_Handle dart_handle, void*); +typedef void (*EvaluateQuickjsByteCodeCallback)(Dart_Handle dart_handle, int8_t); +typedef void (*EvaluateScriptsCallback)(Dart_Handle dart_handle, int8_t); + WEBF_EXPORT_C -void* initDartIsolateContext(uint64_t* dart_methods, int32_t dart_methods_len); +void* initDartIsolateContextSync(int64_t dart_port, uint64_t* dart_methods, int32_t dart_methods_len); + WEBF_EXPORT_C -void* allocateNewPage(void* dart_isolate_context, int32_t targetContextId); +void allocateNewPage(double thread_identity, + void* dart_isolate_context, + Dart_Handle dart_handle, + AllocateNewPageCallback result_callback); WEBF_EXPORT_C -int64_t newPageId(); +void* allocateNewPageSync(double thread_identity, void* dart_isolate_context); WEBF_EXPORT_C -void disposePage(void* dart_isolate_context, void* page); +int64_t newPageIdSync(); + WEBF_EXPORT_C -int8_t evaluateScripts(void* page, - const char* code, - uint64_t code_len, - uint8_t** parsed_bytecodes, - uint64_t* bytecode_len, - const char* bundleFilename, - int32_t startLine); +void disposePage(double dedicated_thread, + void* dart_isolate_context, + void* page, + Dart_Handle dart_handle, + DisposePageCallback result_callback); + WEBF_EXPORT_C -int8_t evaluateQuickjsByteCode(void* page, uint8_t* bytes, int32_t byteLen); +void disposePageSync(double dedicated_thread, void* dart_isolate_context, void* page); + +WEBF_EXPORT_C +void evaluateScripts(void* page, + const char* code, + uint64_t code_len, + uint8_t** parsed_bytecodes, + uint64_t* bytecode_len, + const char* bundleFilename, + int32_t startLine, + Dart_Handle dart_handle, + EvaluateQuickjsByteCodeCallback result_callback); +WEBF_EXPORT_C +void evaluateQuickjsByteCode(void* page, + uint8_t* bytes, + int32_t byteLen, + Dart_Handle dart_handle, + EvaluateQuickjsByteCodeCallback result_callback); WEBF_EXPORT_C void parseHTML(void* page, const char* code, int32_t length); WEBF_EXPORT_C @@ -57,17 +86,28 @@ void* parseSVGResult(const char* code, int32_t length); WEBF_EXPORT_C void freeSVGResult(void* svgTree); WEBF_EXPORT_C -NativeValue* invokeModuleEvent(void* page, - SharedNativeString* module, - const char* eventType, - void* event, - NativeValue* extra); +void invokeModuleEvent(void* page, + SharedNativeString* module, + const char* eventType, + void* event, + NativeValue* extra, + Dart_Handle dart_handle, + InvokeModuleEventCallback result_callback); WEBF_EXPORT_C WebFInfo* getWebFInfo(); WEBF_EXPORT_C void dispatchUITask(void* page, void* context, void* callback); WEBF_EXPORT_C void* getUICommandItems(void* page); +WEBF_EXPORT_C +uint32_t getUICommandKindFlag(void* page); + +WEBF_EXPORT_C +void acquireUiCommandLocks(void* page); + +WEBF_EXPORT_C +void releaseUiCommandLocks(void* page); + WEBF_EXPORT_C int64_t getUICommandItemSize(void* page); WEBF_EXPORT_C @@ -79,6 +119,9 @@ void registerPluginCode(const char* code, int32_t length, const char* pluginName WEBF_EXPORT_C int32_t profileModeEnabled(); +WEBF_EXPORT_C int8_t isJSThreadBlocked(void* dart_isolate_context, double context_id); + +WEBF_EXPORT_C void executeNativeCallback(DartWork* work_ptr); WEBF_EXPORT_C void init_dart_dynamic_linking(void* data); WEBF_EXPORT_C diff --git a/bridge/include/webf_bridge_test.h b/bridge/include/webf_bridge_test.h index de07532cfc..f169ca0a69 100644 --- a/bridge/include/webf_bridge_test.h +++ b/bridge/include/webf_bridge_test.h @@ -11,10 +11,10 @@ WEBF_EXPORT_C void* initTestFramework(void* page); -using ExecuteCallback = void* (*)(int32_t contextId, void* status); +using ExecuteResultCallback = void (*)(Dart_Handle dart_handle, void* result); WEBF_EXPORT_C -void executeTest(void* testContext, ExecuteCallback executeCallback); +void executeTest(void* testContext, Dart_Handle dart_handle, ExecuteResultCallback executeCallback); WEBF_EXPORT_C void registerTestEnvDartMethods(void* testContext, uint64_t* methodBytes, int32_t length); diff --git a/bridge/multiple_threading/dispatcher.cc b/bridge/multiple_threading/dispatcher.cc new file mode 100644 index 0000000000..d417241a6d --- /dev/null +++ b/bridge/multiple_threading/dispatcher.cc @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "dispatcher.h" + +#include "core/dart_isolate_context.h" +#include "core/page.h" +#include "foundation/logging.h" + +using namespace webf; + +namespace webf { + +namespace multi_threading { + +Dispatcher::Dispatcher(Dart_Port dart_port) : dart_port_(dart_port) {} + +Dispatcher::~Dispatcher() {} + +void Dispatcher::AllocateNewJSThread(int32_t js_context_id) { + assert(js_threads_.count(js_context_id) == 0); + js_threads_[js_context_id] = std::make_unique(js_context_id); + js_threads_[js_context_id]->Start(); +} + +bool Dispatcher::IsThreadGroupExist(int32_t js_context_id) { + return js_threads_.count(js_context_id) > 0; +} + +bool Dispatcher::IsThreadBlocked(int32_t js_context_id) { + if (js_threads_.count(js_context_id) == 0) + return false; + + auto& loop = js_threads_[js_context_id]; + return loop->isBlocked(); +} + +void Dispatcher::KillJSThreadSync(int32_t js_context_id) { + assert(js_threads_.count(js_context_id) > 0); + auto& looper = js_threads_[js_context_id]; + PostToJsSync( + true, js_context_id, [](bool cancel, Looper* looper) { looper->ExecuteOpaqueFinalizer(); }, + js_threads_[js_context_id].get()); + looper->Stop(); + js_threads_.erase(js_context_id); +} + +void Dispatcher::SetOpaqueForJSThread(int32_t js_context_id, void* opaque, OpaqueFinalizer finalizer) { + assert(js_threads_.count(js_context_id) > 0); + js_threads_[js_context_id]->SetOpaque(opaque, finalizer); +} + +void* Dispatcher::GetOpaque(int32_t js_context_id) { + assert(js_threads_.count(js_context_id) > 0); + return js_threads_[js_context_id]->opaque(); +} + +void Dispatcher::Dispose(webf::multi_threading::Callback callback) { +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: BEGIN EXE OPAQUE FINALIZER "; +#endif + + for (auto&& thread : js_threads_) { + auto* page_group = static_cast(thread.second->opaque()); + for (auto& page : (*page_group->pages())) { + page->executingContext()->SetContextInValid(); + } + } + + std::set pending_tasks = pending_dart_tasks_; + + for (auto task : pending_tasks) { + const DartWork dart_work = *task; +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: BEGIN EXEC SYNC DART WORKER"; +#endif + dart_work(true); +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: FINISH EXEC SYNC DART WORKER"; +#endif + } + +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: BEGIN FINALIZE ALL JS THREAD"; +#endif + + FinalizeAllJSThreads([this, &callback]() { + StopAllJSThreads(); + callback(); + }); +} + +std::unique_ptr& Dispatcher::looper(int32_t js_context_id) { + assert(js_threads_.count(js_context_id) > 0); + return js_threads_[js_context_id]; +} + +// run in the cpp thread +void Dispatcher::NotifyDart(const DartWork* work_ptr, bool is_sync) { + const intptr_t work_addr = reinterpret_cast(work_ptr); + + Dart_CObject** array = new Dart_CObject*[3]; + + array[0] = new Dart_CObject(); + array[0]->type = Dart_CObject_Type::Dart_CObject_kInt64; + array[0]->value.as_int64 = is_sync ? 1 : 0; + + array[1] = new Dart_CObject(); + array[1]->type = Dart_CObject_Type::Dart_CObject_kInt64; + array[1]->value.as_int64 = work_addr; + + array[2] = new Dart_CObject(); + array[2]->type = Dart_CObject_Type ::Dart_CObject_kInt64; + size_t thread_id = std::hash{}(std::this_thread::get_id()); + array[2]->value.as_int64 = thread_id; + + Dart_CObject dart_object; + dart_object.type = Dart_CObject_kArray; + dart_object.value.as_array.length = 3; + dart_object.value.as_array.values = array; + +#if ENABLE_LOG + if (is_sync) { + WEBF_LOG(WARN) << " SYNC BLOCK THREAD " << std::this_thread::get_id() << " FOR A DART CALLBACK TO RECOVER"; + } +#endif + + const bool result = Dart_PostCObject_DL(dart_port_, &dart_object); + if (!result) { + delete work_ptr; + } + + delete array[0]; + delete array[1]; + delete array[2]; + delete[] array; +} + +void Dispatcher::FinalizeAllJSThreads(webf::multi_threading::Callback callback) { + std::atomic unfinished_thread = js_threads_.size(); + + std::atomic is_final_async_dart_task_complete{false}; + + if (unfinished_thread == 0) { + is_final_async_dart_task_complete = true; + } + + for (auto&& thread : js_threads_) { + PostToJs( + true, thread.first, + [&unfinished_thread, &thread, &is_final_async_dart_task_complete](Looper* looper) { +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: RUN JS FINALIZER, context_id: " << thread.first; +#endif + looper->ExecuteOpaqueFinalizer(); + unfinished_thread--; + +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: UNFINISHED THREAD: " << unfinished_thread; +#endif + if (unfinished_thread == 0) { + is_final_async_dart_task_complete = true; + return; + } + }, + thread.second.get()); +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: POST TO JS THREAD"; +#endif + } + +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: WAITING FOR JS THREAD COMPLETE"; +#endif + // Waiting for the final dart task return. + while (!is_final_async_dart_task_complete) { + } + +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: ALL JS THREAD FINALIZED SUCCESS"; +#endif + callback(); +} + +void Dispatcher::StopAllJSThreads() { +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: FINISH EXEC OPAQUE FINALIZER "; +#endif + for (auto&& thread : js_threads_) { + thread.second->Stop(); + } +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: ALL THREAD STOPPED"; +#endif +} + +} // namespace multi_threading + +} // namespace webf \ No newline at end of file diff --git a/bridge/multiple_threading/dispatcher.h b/bridge/multiple_threading/dispatcher.h new file mode 100644 index 0000000000..6de4c26127 --- /dev/null +++ b/bridge/multiple_threading/dispatcher.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef MULTI_THREADING_DISPATCHER_H +#define MULTI_THREADING_DISPATCHER_H + +#include +#include +#include +#include +#include +#include +#include + +#include "logging.h" +#include "looper.h" +#include "task.h" + +#if defined(_WIN32) +#define WEBF_EXPORT_C extern "C" __declspec(dllexport) +#define WEBF_EXPORT __declspec(dllexport) +#else +#define WEBF_EXPORT_C extern "C" __attribute__((visibility("default"))) __attribute__((used)) +#define WEBF_EXPORT __attribute__((__visibility__("default"))) +#endif + +namespace webf { + +namespace multi_threading { + +/** + * @brief thread dispatcher, used to dispatch tasks to dart thread or js thread. + * + */ +class Dispatcher { + public: + explicit Dispatcher(Dart_Port dart_port); + ~Dispatcher(); + + void AllocateNewJSThread(int32_t js_context_id); + bool IsThreadGroupExist(int32_t js_context_id); + bool IsThreadBlocked(int32_t js_context_id); + void KillJSThreadSync(int32_t js_context_id); + void SetOpaqueForJSThread(int32_t js_context_id, void* opaque, OpaqueFinalizer finalizer); + void* GetOpaque(int32_t js_context_id); + void Dispose(Callback callback); + + std::unique_ptr& looper(int32_t js_context_id); + + template + void PostToDart(bool dedicated_thread, Func&& func, Args&&... args) { + if (!dedicated_thread) { + std::invoke(std::forward(func), std::forward(args)...); + return; + } + + auto task = std::make_shared>(std::forward(func), std::forward(args)...); + DartWork work = [task](bool cancel) { (*task)(); }; + + const DartWork* work_ptr = new DartWork(work); + NotifyDart(work_ptr, false); + } + + template + void PostToDartAndCallback(bool dedicated_thread, Func&& func, Callback callback, Args&&... args) { + if (!dedicated_thread) { + std::invoke(std::forward(func), std::forward(args)...); + callback(); + return; + } + + auto task = std::make_shared>( + std::forward(func), std::forward(args)..., std::forward(callback)); + const DartWork work = [task]() { (*task)(); }; + + const DartWork* work_ptr = new DartWork(work); + NotifyDart(work_ptr, false); + } + + template + auto PostToDartSync(bool dedicated_thread, double js_context_id, Func&& func, Args&&... args) + -> std::invoke_result_t { + if (!dedicated_thread) { + return std::invoke(std::forward(func), false, std::forward(args)...); + } + + auto task = + std::make_shared>(std::forward(func), std::forward(args)...); + auto thread_group_id = static_cast(js_context_id); + auto& looper = js_threads_[thread_group_id]; + const DartWork work = [task, &looper](bool cancel) { +#if ENABLE_LOG + WEBF_LOG(WARN) << " BLOCKED THREAD " << std::this_thread::get_id() << " HAD BEEN RESUMED" + << " is_cancel: " << cancel; +#endif + + looper->is_blocked_ = false; + (*task)(cancel); + }; + + DartWork* work_ptr = new DartWork(work); + pending_dart_tasks_.insert(work_ptr); + + NotifyDart(work_ptr, true); + + looper->is_blocked_ = true; + task->wait(); + pending_dart_tasks_.erase(work_ptr); + + return task->getResult(); + } + + // template + // void PostToDartWithoutResSync(bool dedicated_thread, Func&& func, Args&&... args) { + // if (!dedicated_thread) { + // std::invoke(std::forward(func), std::forward(args)...); + // } + // + // auto task = + // std::make_shared>(std::forward(func), std::forward(args)...); + // const DartWork work = [task]() { (*task)(); }; + // + // const DartWork* work_ptr = new DartWork(work); + // NotifyDart(work_ptr, true); + // + // task->wait(); + // } + + template + void PostToJs(bool dedicated_thread, int32_t js_context_id, Func&& func, Args&&... args) { + if (!dedicated_thread) { + std::invoke(std::forward(func), std::forward(args)...); + return; + } + + assert(js_threads_.count(js_context_id) > 0); + auto& looper = js_threads_[js_context_id]; + looper->PostMessage(std::forward(func), std::forward(args)...); + } + + template + void PostToJsAndCallback(bool dedicated_thread, + int32_t js_context_id, + Func&& func, + Callback&& callback, + Args&&... args) { + if (!dedicated_thread) { + std::invoke(std::forward(func), std::forward(args)...); + callback(); + return; + } + + assert(js_threads_.count(js_context_id) > 0); + auto& looper = js_threads_[js_context_id]; + looper->PostMessageAndCallback(std::forward(func), std::forward(callback), + std::forward(args)...); + } + + template + auto PostToJsSync(bool dedicated_thread, int32_t js_context_id, Func&& func, Args&&... args) + -> std::invoke_result_t { + if (!dedicated_thread) { + return std::invoke(std::forward(func), false, std::forward(args)...); + } + + assert(js_threads_.count(js_context_id) > 0); + auto& looper = js_threads_[js_context_id]; + return looper->PostMessageSync(std::forward(func), std::forward(args)...); + } + + private: + void NotifyDart(const DartWork* work_ptr, bool is_sync); + + void FinalizeAllJSThreads(Callback callback); + void StopAllJSThreads(); + + private: + Dart_Port dart_port_; + std::unordered_map> js_threads_; + std::set pending_dart_tasks_; + friend Looper; +}; + +} // namespace multi_threading + +} // namespace webf + +#endif // MULTI_THREADING_DISPATCHER_H \ No newline at end of file diff --git a/bridge/multiple_threading/looper.cc b/bridge/multiple_threading/looper.cc new file mode 100644 index 0000000000..bca9505aed --- /dev/null +++ b/bridge/multiple_threading/looper.cc @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "looper.h" +#include + +#include + +#include "logging.h" + +namespace webf { + +namespace multi_threading { + +static void setThreadName(const std::string& name) { +#if defined(__APPLE__) && defined(__MACH__) // Apple OSX and iOS (Darwin) + pthread_setname_np(name.c_str()); +#elif defined(__ANDROID__) + pthread_setname_np(pthread_self(), name.c_str()); +#endif +} + +Looper::Looper(int32_t js_id) : js_id_(js_id), running_(false), paused_(false) {} + +Looper::~Looper() {} + +void Looper::Start() { + std::lock_guard lock(mutex_); + if (!worker_.joinable()) { + running_ = true; + worker_ = std::thread([this] { + std::string thread_name = "JS Worker " + std::to_string(js_id_); + setThreadName(thread_name.c_str()); + this->Run(); + }); + } +} + +void Looper::Stop() { + { + std::lock_guard lock(mutex_); + running_ = false; + } + cv_.notify_one(); + if (worker_.joinable()) { + worker_.join(); + } +} + +// private methods +void Looper::Run() { + while (true) { + std::shared_ptr task = nullptr; + { + std::unique_lock lock(mutex_); + cv_.wait(lock, [this] { return !running_ || (!tasks_.empty() && !paused_); }); + + if (!running_) { + return; + } + + if (!paused_ && !tasks_.empty()) { + task = std::move(tasks_.front()); + tasks_.pop(); + } + } + if (task != nullptr && running_) { + (*task)(false); + } + } +} + +void Looper::SetOpaque(void* p, OpaqueFinalizer finalizer) { + opaque_ = p; + opaque_finalizer_ = finalizer; +} + +bool Looper::isBlocked() { + return is_blocked_; +} + +void* Looper::opaque() { + return opaque_; +} + +void Looper::ExecuteOpaqueFinalizer() { + opaque_finalizer_(opaque_); +} + +} // namespace multi_threading + +} // namespace webf diff --git a/bridge/multiple_threading/looper.h b/bridge/multiple_threading/looper.h new file mode 100644 index 0000000000..a48dabdb3d --- /dev/null +++ b/bridge/multiple_threading/looper.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef MULTI_THREADING_LOOPER_H_ +#define MULTI_THREADING_LOOPER_H_ + +#include +#include +#include +#include +#include + +#include "foundation/logging.h" +#include "task.h" + +namespace webf { + +namespace multi_threading { + +typedef void (*OpaqueFinalizer)(void* p); + +class Dispatcher; + +/** + * @brief thread looper, used to Run tasks in a thread. + * + */ +class Looper { + public: + Looper(int32_t js_id); + ~Looper(); + + void Start(); + + template + void PostMessage(Func&& func, Args&&... args) { + auto task = std::make_shared>(std::forward(func), std::forward(args)...); + { + std::unique_lock lock(mutex_); + tasks_.emplace(std::move(task)); + } + cv_.notify_one(); + } + + template + void PostMessageAndCallback(Func&& func, Callback&& callback, Args&&... args) { + auto task = std::make_shared>( + std::forward(func), std::forward(args)..., std::forward(callback)); + { + std::unique_lock lock(mutex_); + tasks_.emplace(std::move(task)); + } + cv_.notify_one(); + } + + template + auto PostMessageSync(Func&& func, Args&&... args) -> std::invoke_result_t { + auto task = + std::make_shared>(std::forward(func), std::forward(args)...); + auto task_copy = task; + { + std::unique_lock lock(mutex_); + tasks_.emplace(std::move(task)); + } + cv_.notify_one(); + task_copy->wait(); + + return task_copy->getResult(); + } + + void Stop(); + + void SetOpaque(void* p, OpaqueFinalizer finalizer); + void* opaque(); + + bool isBlocked(); + + void ExecuteOpaqueFinalizer(); + + private: + void Run(); + + std::condition_variable cv_; + std::mutex mutex_; + std::queue> tasks_; + std::thread worker_; + bool paused_; + bool running_; + void* opaque_; + OpaqueFinalizer opaque_finalizer_; + int32_t js_id_; + std::atomic is_blocked_; + friend Dispatcher; +}; + +} // namespace multi_threading + +} // namespace webf + +#endif // MULTI_THREADING_LOOPER_H_ diff --git a/bridge/multiple_threading/task.h b/bridge/multiple_threading/task.h new file mode 100644 index 0000000000..07ba3488f2 --- /dev/null +++ b/bridge/multiple_threading/task.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef MULTI_THREADING_TASK_H +#define MULTI_THREADING_TASK_H + +#include +#include +#include +#include + +#include "foundation/logging.h" + +namespace webf { + +namespace multi_threading { + +using Callback = std::function; + +class Task { + public: + virtual ~Task() = default; + virtual void operator()(bool cancel = false) = 0; +}; + +template +class ConcreteTask : public Task { + public: + ConcreteTask(Func&& f, Args&&... args) : func_(std::bind(std::forward(f), std::forward(args)...)) {} + + void operator()(bool cancel = false) override { + if (func_) { + func_(); + } + } + + private: + std::function func_; +}; + +template +class ConcreteCallbackTask : public Task { + public: + ConcreteCallbackTask(Func&& f, Args&&... args, Callback&& callback) + : func_(std::bind(std::forward(f), std::forward(args)...)), + callback_(std::forward(callback)) {} + + void operator()(bool cancel = false) override { + if (func_) { + func_(); + } + if (callback_) { + callback_(); + } + } + + private: + std::function func_; + Callback callback_; +}; + +class SyncTask : public Task { + public: + virtual ~SyncTask() = default; + virtual void wait() = 0; +}; + +template +class ConcreteSyncTask : public SyncTask { + public: + using ReturnType = std::invoke_result_t; + + ConcreteSyncTask(Func&& func, Args&&... args) + : task_(std::bind(std::forward(func), std::placeholders::_1, std::forward(args)...)), + future_(task_.get_future()) {} + + void operator()(bool cancel = false) override { +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[ConcreteSyncTask]: CALL SYNC CONCRETE TASK"; +#endif + task_(cancel); + } + + void wait() override { +#ifdef DDEBUG + future_.wait(); +#else + auto status = future_.wait_for(std::chrono::milliseconds(2000)); + if (status == std::future_status::timeout) { + WEBF_LOG(ERROR) << "SyncTask wait timeout" << std::endl; + } +#endif + } + + ReturnType getResult() { return future_.get(); } + + private: + std::packaged_task task_; + std::future future_; +}; + +} // namespace multi_threading + +} // namespace webf + +#endif // MULTI_THREADING_TASK_H \ No newline at end of file diff --git a/bridge/polyfill/src/test/index.js b/bridge/polyfill/src/test/index.js index 7d341fee3f..8deec71e81 100644 --- a/bridge/polyfill/src/test/index.js +++ b/bridge/polyfill/src/test/index.js @@ -173,5 +173,6 @@ __webf_execute_test__((done) => { // Trigger global js exception to test window.onerror. __webf_trigger_global_error__(); + console.log('start exec test'); env.execute(); }); diff --git a/bridge/scripts/code_generator/global.d.ts b/bridge/scripts/code_generator/global.d.ts index 9d8f74d6f8..d34b2abc9a 100644 --- a/bridge/scripts/code_generator/global.d.ts +++ b/bridge/scripts/code_generator/global.d.ts @@ -14,3 +14,6 @@ declare type LegacyNullToEmptyString = string | null; // This property is implemented by Dart side type DartImpl = T; type StaticMember = T; + + +type DependentsOnLayout = T; \ No newline at end of file diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 2d83c6d725..6e0ae6d27a 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -127,7 +127,21 @@ function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): Paramete return argument.typeName.text; } else if (identifier === 'DartImpl') { if (mode) mode.dartImpl = true; - let argument = typeReference.typeArguments![0]; + let argument: ts.TypeNode = typeReference.typeArguments![0] as unknown as ts.TypeNode; + + if (argument.kind == ts.SyntaxKind.TypeReference) { + let typeReference: ts.TypeReference = argument as unknown as ts.TypeReference; + // @ts-ignore + let identifier = (typeReference.typeName as ts.Identifier).text; + + if (identifier == 'DependentsOnLayout') { + if (mode) { + mode.layoutDependent = true; + } + argument = typeReference.typeArguments![0] as unknown as ts.TypeNode; + } + } + // @ts-ignore return getParameterBaseType(argument); } else if (identifier === 'StaticMember') { diff --git a/bridge/scripts/code_generator/src/idl/declaration.ts b/bridge/scripts/code_generator/src/idl/declaration.ts index 20fbdd0354..2bb628d8f8 100644 --- a/bridge/scripts/code_generator/src/idl/declaration.ts +++ b/bridge/scripts/code_generator/src/idl/declaration.ts @@ -32,6 +32,7 @@ export class FunctionArguments { export class ParameterMode { newObject?: boolean; dartImpl?: boolean; + layoutDependent?: boolean; static?: boolean; } diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index b732f5e9fe..30b5793a41 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -334,7 +334,7 @@ function generateCallMethodName(name: string) { return name; } -function generateDartImplCallCode(blob: IDLBlob, declare: FunctionDeclaration, args: FunctionArguments[]): string { +function generateDartImplCallCode(blob: IDLBlob, declare: FunctionDeclaration, isLayoutIndependent: boolean, args: FunctionArguments[]): string { let nativeArguments = args.map(i => { return `NativeValueConverter<${generateNativeValueTypeConverter(i.type)}>::ToNativeValue(${isDOMStringType(i.type) ? 'ctx, ' : ''}args_${i.name})`; }); @@ -350,7 +350,7 @@ auto* self = toScriptWrappable<${getClassName(blob)}>(JS_IsUndefined(this_val) ? ${nativeArguments.length > 0 ? `NativeValue arguments[] = { ${nativeArguments.join(',\n')} }` : 'NativeValue* arguments = nullptr;'}; -${returnValueAssignment}self->InvokeBindingMethod(binding_call_methods::k${declare.name}, ${nativeArguments.length}, arguments, exception_state); +${returnValueAssignment}self->InvokeBindingMethod(binding_call_methods::k${declare.name}, ${nativeArguments.length}, arguments, FlushUICommandReason::kDependentsOnElement${isLayoutIndependent ? '| FlushUICommandReason::kDependentsOnElement' : ''}, exception_state); ${returnValueAssignment.length > 0 ? `return Converter<${generateIDLTypeConverter(declare.returnType)}>::ToValue(NativeValueConverter<${generateNativeValueTypeConverter(declare.returnType)}>::FromNativeValue(native_value))` : ''}; `.trim(); } @@ -362,7 +362,7 @@ function generateOptionalInitBody(blob: IDLBlob, declare: FunctionDeclaration, a returnValueAssignment = 'return_value ='; } if (declare.returnTypeMode?.dartImpl) { - call = generateDartImplCallCode(blob, declare, declare.args.slice(0, argsIndex + 1)); + call = generateDartImplCallCode(blob, declare, declare.returnTypeMode?.layoutDependent ?? false, declare.args.slice(0, argsIndex + 1)); } else if (options.isInstanceMethod) { call = `auto* self = toScriptWrappable<${getClassName(blob)}>(JS_IsUndefined(this_val) ? context->Global() : this_val); ${returnValueAssignment} self->${generateCallMethodName(declare.name)}(${[...previousArguments, `args_${argument.name}`, 'exception_state'].join(',')});`; @@ -420,7 +420,7 @@ function generateFunctionCallBody(blob: IDLBlob, declaration: FunctionDeclaratio returnValueAssignment = 'return_value ='; } if (declaration.returnTypeMode?.dartImpl) { - call = generateDartImplCallCode(blob, declaration, declaration.args.slice(0, minimalRequiredArgc)); + call = generateDartImplCallCode(blob, declaration, declaration.returnTypeMode?.layoutDependent ?? false, declaration.args.slice(0, minimalRequiredArgc)); } else if (options.isInstanceMethod) { call = `auto* self = toScriptWrappable<${getClassName(blob)}>(JS_IsUndefined(this_val) ? context->Global() : this_val); ${returnValueAssignment} self->${generateCallMethodName(declaration.name)}(${minimalRequiredArgc > 0 ? `${requiredArguments.join(',')}` : 'exception_state'});`; @@ -529,7 +529,8 @@ function generateFunctionBody(blob: IDLBlob, declare: FunctionDeclaration, optio ExceptionState exception_state; ExecutingContext* context = ExecutingContext::From(ctx); - MemberMutationScope scope{ExecutingContext::From(ctx)}; + if (!context->IsContextValid()) return JS_NULL; + MemberMutationScope scope{context}; ${returnValueInit} do { // Dummy loop for use of 'break'. diff --git a/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl index ceff5e264e..83bdff46f0 100644 --- a/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl @@ -9,6 +9,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return false; auto* wrapper_type_info = DOMTokenList::GetStaticWrapperTypeInfo(); MemberMutationScope scope{context}; JSValue prototype = context->contextData()->prototypeForType(wrapper_type_info); @@ -22,7 +23,9 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob int QJS<%= className %>::PropertyEnumerateCallback(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, JSValue obj) { auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return 0; + MemberMutationScope scope{context}; std::vector props; self->NamedPropertyEnumerator(props, exception_state); auto size = props.size() == 0 ? 1 : props.size(); @@ -40,7 +43,9 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob <% if (object.indexedProp.indexKeyType == 'number') { %> JSValue QJS<%= className %>::IndexedPropertyGetterCallback(JSContext* ctx, JSValue obj, uint32_t index) { ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return JS_NULL; + MemberMutationScope scope{context}; auto* self = toScriptWrappable<<%= className %>>(obj); if (index >= self->length()) { return JS_UNDEFINED; @@ -56,7 +61,9 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob JSValue QJS<%= className %>::StringPropertyGetterCallback(JSContext* ctx, JSValue obj, JSAtom key) { auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return JS_NULL; + MemberMutationScope scope{context}; ${generateCoreTypeValue(object.indexedProp.type)} result = self->item(AtomicString(ctx, key), exception_state); if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); @@ -69,7 +76,9 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob bool QJS<%= className %>::IndexedPropertySetterCallback(JSContext* ctx, JSValueConst obj, uint32_t index, JSValueConst value) { auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return false; + MemberMutationScope scope{context}; auto&& v = Converter<<%= generateIDLTypeConverter(object.indexedProp.type, object.indexedProp.optional) %>>::FromValue(ctx, value, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; @@ -84,7 +93,9 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob bool QJS<%= className %>::StringPropertySetterCallback(JSContext* ctx, JSValueConst obj, JSAtom key, JSValueConst value) { auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return false; + MemberMutationScope scope{context}; auto&& v = Converter<<%= generateIDLTypeConverter(object.indexedProp.type, object.indexedProp.optional) %>>::FromValue(ctx, value, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; @@ -99,7 +110,9 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob bool QJS<%= className %>::StringPropertyDeleterCallback(JSContext* ctx, JSValueConst obj, JSAtom key) { auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return false; + MemberMutationScope scope{context}; if (UNLIKELY(exception_state.HasException())) { return false; } @@ -150,14 +163,16 @@ static JSValue <%= prop.name %>AttributeGetCallback(JSContext* ctx, JSValueConst auto* <%= blob.filename %> = toScriptWrappable<<%= className %>>(this_val); assert(<%= blob.filename %> != nullptr); - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return JS_NULL; + MemberMutationScope scope{context}; <% if (prop.typeMode && prop.typeMode.dartImpl) { %> ExceptionState exception_state; <% if (isTypeNeedAllocate(prop.type)) { %> - typename <%= generateNativeValueTypeConverter(prop.type) %>::ImplType v = NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::FromNativeValue(ctx, <%= blob.filename %>->GetBindingProperty(binding_call_methods::k<%= prop.name %>, exception_state)); + typename <%= generateNativeValueTypeConverter(prop.type) %>::ImplType v = NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::FromNativeValue(ctx, <%= blob.filename %>->GetBindingProperty(binding_call_methods::k<%= prop.name %>, FlushUICommandReason::kDependentsOnElement <%= prop.typeMode.layoutDependent ? '| FlushUICommandReason::kDependentsOnLayout' : '' %>, exception_state)); <% } else { %> - typename <%= generateNativeValueTypeConverter(prop.type) %>::ImplType v = NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::FromNativeValue(<%= blob.filename %>->GetBindingProperty(binding_call_methods::k<%= prop.name %>, exception_state)); + typename <%= generateNativeValueTypeConverter(prop.type) %>::ImplType v = NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::FromNativeValue(<%= blob.filename %>->GetBindingProperty(binding_call_methods::k<%= prop.name %>, FlushUICommandReason::kDependentsOnElement <%= prop.typeMode.layoutDependent ? '| FlushUICommandReason::kDependentsOnLayout' : '' %>, exception_state)); <% } %> if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); @@ -175,7 +190,9 @@ static JSValue <%= prop.name %>AttributeGetCallback(JSContext* ctx, JSValueConst static JSValue <%= prop.name %>AttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { auto* <%= blob.filename %> = toScriptWrappable<<%= className %>>(this_val); ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return JS_NULL; + MemberMutationScope scope{context}; auto&& v = Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::FromValue(ctx, argv[0], exception_state); if (exception_state.HasException()) { return exception_state.ToQuickJS(); @@ -202,14 +219,18 @@ static JSValue <%= prop.name %>AttributeSetCallback(JSContext* ctx, JSValueConst static JSValue <%= prop.name %>AttributeGetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { auto* <%= blob.filename %> = toScriptWrappable<<%= className %>>(this_val); assert(<%= blob.filename %> != nullptr); - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return JS_NULL; + MemberMutationScope scope{context}; return Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::ToValue(ctx, <%= object.name %>::<%= prop.name %>(*<%= blob.filename %>)); } <% if (!prop.readonly) { %> static JSValue <%= prop.name %>AttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { auto* <%= blob.filename %> = toScriptWrappable<<%= className %>>(this_val); ExceptionState exception_state; - MemberMutationScope scope{ExecutingContext::From(ctx)}; + ExecutingContext* context = ExecutingContext::From(ctx); + if (!context->IsContextValid()) return JS_NULL; + MemberMutationScope scope{context}; auto&& v = Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::FromValue(ctx, argv[0], exception_state); if (exception_state.HasException()) { return exception_state.ToQuickJS(); diff --git a/bridge/test/benchmark/create_element.cc b/bridge/test/benchmark/create_element.cc index 52fb38ab87..7ae3743252 100644 --- a/bridge/test/benchmark/create_element.cc +++ b/bridge/test/benchmark/create_element.cc @@ -11,7 +11,7 @@ using namespace webf; auto env = TEST_init(); static void CreateRawJavaScriptObjects(benchmark::State& state) { - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); uint8_t bytes[] = {1, 2, 2, 97, 12, 97, 97, 97, 46, 106, 115, 14, 0, 6, 0, 160, 1, 0, 1, 0, 1, 0, 0, 20, 1, 162, 1, 0, 0, 0, 63, 210, 0, 0, 0, 0, 62, 210, 0, 0, 0, 0, 11, 57, 210, 0, 0, 0, 195, 40, 166, 3, 1, 2, 31, 33}; @@ -22,7 +22,7 @@ static void CreateRawJavaScriptObjects(benchmark::State& state) { } static void CreateDivElement(benchmark::State& state) { - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); std::string code = R"( (() => { let container = document.createElement('div'); @@ -45,7 +45,7 @@ for(let i = 0; i < 1000; i ++) { } static void InsertElement(benchmark::State& state) { - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); std::string code = R"( (() => { let container = document.createElement('div'); diff --git a/bridge/test/run_integration_test.cc b/bridge/test/run_integration_test.cc index 64aa1f66d9..35b452597f 100644 --- a/bridge/test/run_integration_test.cc +++ b/bridge/test/run_integration_test.cc @@ -35,7 +35,7 @@ std::string readTestSpec() { // Very useful to fix bridge bugs. TEST(IntegrationTest, runSpecs) { auto env = TEST_init(); - auto context = env->page()->GetExecutingContext(); + auto context = env->page()->executingContext(); std::string code = readTestSpec(); env->page()->evaluateScript(code.c_str(), code.size(), "vm://", 0); diff --git a/bridge/test/webf_test_context.cc b/bridge/test/webf_test_context.cc index 7cfb206068..7cd2548989 100644 --- a/bridge/test/webf_test_context.cc +++ b/bridge/test/webf_test_context.cc @@ -59,43 +59,49 @@ static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int arg ctx, "Failed to execute '__webf_match_image_snapshot__': parameter 3 (callback) is not an function."); } - if (context->dartMethodPtr()->matchImageSnapshot == nullptr) { - return JS_ThrowTypeError( - ctx, "Failed to execute '__webf_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); - } - auto* callbackContext = new ImageSnapShotContext{JS_DupValue(ctx, callbackValue), context}; - auto fn = [](void* ptr, int32_t contextId, int8_t result, const char* errmsg) { - auto* callbackContext = static_cast(ptr); - JSContext* ctx = callbackContext->context->ctx(); - - if (errmsg == nullptr) { - JSValue arguments[] = {JS_NewBool(ctx, result != 0), JS_NULL}; - JSValue returnValue = JS_Call(ctx, callbackContext->callback, callbackContext->context->Global(), 1, arguments); - callbackContext->context->HandleException(&returnValue); - } else { - JSValue errmsgValue = JS_NewString(ctx, errmsg); - JSValue arguments[] = {JS_NewBool(ctx, false), errmsgValue}; - JSValue returnValue = JS_Call(ctx, callbackContext->callback, callbackContext->context->Global(), 2, arguments); - callbackContext->context->HandleException(&returnValue); - JS_FreeValue(ctx, errmsgValue); - } - - callbackContext->context->DrainMicrotasks(); - JS_FreeValue(callbackContext->context->ctx(), callbackContext->callback); - delete callbackContext; + auto fn = [](void* ptr, double contextId, int8_t result, char* errmsg) { + auto* callback_context = static_cast(ptr); + auto* context = callback_context->context; + + callback_context->context->dartIsolateContext()->dispatcher()->PostToJs( + context->isDedicated(), context->contextId(), + [](ImageSnapShotContext* callback_context, int8_t result, char* errmsg) { + JSContext* ctx = callback_context->context->ctx(); + + if (errmsg == nullptr) { + JSValue arguments[] = {JS_NewBool(ctx, result != 0), JS_NULL}; + JSValue returnValue = + JS_Call(ctx, callback_context->callback, callback_context->context->Global(), 1, arguments); + callback_context->context->HandleException(&returnValue); + } else { + JSValue errmsgValue = JS_NewString(ctx, errmsg); + JSValue arguments[] = {JS_NewBool(ctx, false), errmsgValue}; + JSValue returnValue = + JS_Call(ctx, callback_context->callback, callback_context->context->Global(), 2, arguments); + callback_context->context->HandleException(&returnValue); + JS_FreeValue(ctx, errmsgValue); + dart_free(errmsg); + } + + callback_context->context->DrainMicrotasks(); + JS_FreeValue(callback_context->context->ctx(), callback_context->callback); + delete callback_context; + }, + callback_context, result, errmsg); }; if (QJSBlob::HasInstance(context, screenShotValue)) { auto* expectedBlob = toScriptWrappable(screenShotValue); - context->dartMethodPtr()->matchImageSnapshotBytes(callbackContext, context->contextId(), blob->bytes(), - blob->size(), expectedBlob->bytes(), expectedBlob->size(), fn); + context->dartMethodPtr()->matchImageSnapshotBytes(context->isDedicated(), callbackContext, context->contextId(), + blob->bytes(), blob->size(), expectedBlob->bytes(), + expectedBlob->size(), fn); } else { std::unique_ptr screenShotNativeString = webf::jsValueToNativeString(ctx, screenShotValue); - context->dartMethodPtr()->matchImageSnapshot(callbackContext, context->contextId(), blob->bytes(), blob->size(), - screenShotNativeString.release(), fn); + context->dartMethodPtr()->matchImageSnapshot(context->isDedicated(), callbackContext, context->contextId(), + blob->bytes(), blob->size(), screenShotNativeString.release(), fn); } return JS_NULL; @@ -104,11 +110,7 @@ static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int arg static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { auto* context = ExecutingContext::From(ctx); #if FLUTTER_BACKEND - if (context->dartMethodPtr()->environment == nullptr) { - return JS_ThrowTypeError(ctx, - "Failed to execute '__webf_environment__': dart method (environment) is not registered."); - } - const char* env = context->dartMethodPtr()->environment(); + const char* env = context->dartMethodPtr()->environment(context->isDedicated(), context->contextId()); return JS_ParseJSON(ctx, env, strlen(env), ""); #else return JS_NewObject(ctx); @@ -120,23 +122,24 @@ struct SimulatePointerCallbackContext { JSValue callbackValue{JS_NULL}; }; -static void handleSimulatePointerCallback(void* p, int32_t contextId, const char* errmsg) { +static void handleSimulatePointerCallback(void* p, double contextId, char* errmsg) { auto* simulate_context = static_cast(p); - JSValue return_value = - JS_Call(simulate_context->context->ctx(), simulate_context->callbackValue, JS_NULL, 0, nullptr); - JS_FreeValue(simulate_context->context->ctx(), return_value); - JS_FreeValue(simulate_context->context->ctx(), simulate_context->callbackValue); - simulate_context->context->DrainMicrotasks(); - delete simulate_context; + auto* context = simulate_context->context; + context->dartIsolateContext()->dispatcher()->PostToJs( + context->isDedicated(), context->contextId(), + [](SimulatePointerCallbackContext* simulate_context, double contextId, char* errmsg) { + JSValue return_value = + JS_Call(simulate_context->context->ctx(), simulate_context->callbackValue, JS_NULL, 0, nullptr); + JS_FreeValue(simulate_context->context->ctx(), return_value); + JS_FreeValue(simulate_context->context->ctx(), simulate_context->callbackValue); + simulate_context->context->DrainMicrotasks(); + delete simulate_context; + }, + simulate_context, contextId, errmsg); } static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { auto* context = static_cast(JS_GetContextOpaque(ctx)); - if (context->dartMethodPtr()->simulatePointer == nullptr) { - return JS_ThrowTypeError( - ctx, "Failed to execute '__webf_simulate_pointer__': dart method(simulatePointer) is not registered."); - } - JSValue inputArrayValue = argv[0]; if (!JS_IsObject(inputArrayValue)) { return JS_ThrowTypeError(ctx, "Failed to execute '__webf_simulate_pointer__': first arguments should be an array."); @@ -213,20 +216,14 @@ static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, auto* simulate_context = new SimulatePointerCallbackContext(); simulate_context->context = context; simulate_context->callbackValue = JS_DupValue(ctx, callbackValue); - context->dartMethodPtr()->simulatePointer(simulate_context, mousePointerList, length, pointer, + context->dartMethodPtr()->simulatePointer(context->isDedicated(), simulate_context, mousePointerList, length, pointer, handleSimulatePointerCallback); - delete[] mousePointerList; - return JS_NULL; } static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { auto* context = static_cast(JS_GetContextOpaque(ctx)); - if (context->dartMethodPtr()->simulateInputText == nullptr) { - return JS_ThrowTypeError( - ctx, "Failed to execute '__webf_simulate_keypress__': dart method(simulateInputText) is not registered."); - } JSValue& charStringValue = argv[0]; @@ -236,7 +233,7 @@ static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc std::unique_ptr nativeString = webf::jsValueToNativeString(ctx, charStringValue); void* p = static_cast(nativeString.get()); - context->dartMethodPtr()->simulateInputText(static_cast(p)); + context->dartMethodPtr()->simulateInputText(context->isDedicated(), static_cast(p)); return JS_NULL; }; @@ -270,15 +267,21 @@ struct ExecuteCallbackContext { ExecuteCallbackContext() = delete; explicit ExecuteCallbackContext(ExecutingContext* context, - ExecuteCallback executeCallback, - WebFTestContext* webf_context) - : executeCallback(executeCallback), context(context), webf_context(webf_context){}; - ExecuteCallback executeCallback; + ExecuteResultCallback executeCallback, + WebFTestContext* webf_context, + Dart_PersistentHandle persistent_handle) + : executeCallback(executeCallback), + context(context), + webf_context(webf_context), + persistent_handle(persistent_handle){}; + ExecuteResultCallback executeCallback; ExecutingContext* context; WebFTestContext* webf_context; + Dart_PersistentHandle persistent_handle; }; -void WebFTestContext::invokeExecuteTest(ExecuteCallback executeCallback) { +void WebFTestContext::invokeExecuteTest(Dart_PersistentHandle persistent_handle, + ExecuteResultCallback executeCallback) { if (execute_test_callback_ == nullptr) { return; } @@ -296,12 +299,21 @@ void WebFTestContext::invokeExecuteTest(ExecuteCallback executeCallback) { WEBF_LOG(VERBOSE) << "Done.."; std::unique_ptr status = webf::jsValueToNativeString(ctx, statusValue); - callbackContext->executeCallback(callbackContext->context->contextId(), status.get()); + + callbackContext->context->dartIsolateContext()->dispatcher()->PostToDart( + callbackContext->context->isDedicated(), + [](ExecuteCallbackContext* callback_context, SharedNativeString* status) { + Dart_Handle handle = Dart_HandleFromPersistent_DL(callback_context->persistent_handle); + callback_context->executeCallback(handle, status); + Dart_DeletePersistentHandle_DL(callback_context->persistent_handle); + callback_context->webf_context->execute_test_proxy_object_ = JS_NULL; + }, + callbackContext, status.release()); JS_FreeValue(ctx, proxyObject); - callbackContext->webf_context->execute_test_proxy_object_ = JS_NULL; return JS_NULL; }; - auto* callbackContext = new ExecuteCallbackContext(context_, executeCallback, this); + + auto* callbackContext = new ExecuteCallbackContext(context_, executeCallback, this, persistent_handle); execute_test_proxy_object_ = JS_NewObject(context_->ctx()); JS_SetOpaque(execute_test_proxy_object_, callbackContext); JSValue callbackData[]{execute_test_proxy_object_}; @@ -349,14 +361,14 @@ bool WebFTestContext::parseTestHTML(const uint16_t* code, size_t codeLength) { void WebFTestContext::registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length) { size_t i = 0; - auto& dartMethodPtr = context_->dartMethodPtr(); + auto dartMethodPtr = context_->dartMethodPtr(); - dartMethodPtr->onJsError = reinterpret_cast(methodBytes[i++]); - dartMethodPtr->matchImageSnapshot = reinterpret_cast(methodBytes[i++]); - dartMethodPtr->matchImageSnapshotBytes = reinterpret_cast(methodBytes[i++]); - dartMethodPtr->environment = reinterpret_cast(methodBytes[i++]); - dartMethodPtr->simulatePointer = reinterpret_cast(methodBytes[i++]); - dartMethodPtr->simulateInputText = reinterpret_cast(methodBytes[i++]); + dartMethodPtr->SetOnJSError(reinterpret_cast(methodBytes[i++])); + dartMethodPtr->SetMatchImageSnapshot(reinterpret_cast(methodBytes[i++])); + dartMethodPtr->SetMatchImageSnapshotBytes(reinterpret_cast(methodBytes[i++])); + dartMethodPtr->SetEnvironment(reinterpret_cast(methodBytes[i++])); + dartMethodPtr->SetSimulatePointer(reinterpret_cast(methodBytes[i++])); + dartMethodPtr->SetSimulateInputText(reinterpret_cast(methodBytes[i++])); assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); } diff --git a/bridge/test/webf_test_context.h b/bridge/test/webf_test_context.h index ed40b3632c..b7222dc611 100644 --- a/bridge/test/webf_test_context.h +++ b/bridge/test/webf_test_context.h @@ -27,7 +27,7 @@ class WebFTestContext final { /// Evaluate JavaScript source code with build-in test frameworks, use in test only. bool parseTestHTML(const uint16_t* code, size_t codeLength); - void invokeExecuteTest(ExecuteCallback executeCallback); + void invokeExecuteTest(Dart_PersistentHandle persistent_handle, ExecuteResultCallback executeCallback); void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length); WebFPage* page() const { return page_; } diff --git a/bridge/test/webf_test_env.cc b/bridge/test/webf_test_env.cc index af4834b26d..b86c77c912 100644 --- a/bridge/test/webf_test_env.cc +++ b/bridge/test/webf_test_env.cc @@ -25,7 +25,7 @@ typedef struct { struct list_head link; int64_t timeout; webf::DOMTimer* timer; - int32_t contextId; + double contextId; bool isInterval; AsyncCallback func; } JSOSTimer; @@ -33,7 +33,7 @@ typedef struct { typedef struct { struct list_head link; webf::FrameCallback* callback; - int32_t contextId; + double contextId; AsyncRAFCallback handler; int32_t callbackId; } JSFrameCallback; @@ -52,7 +52,7 @@ static void unlink_callback(JSThreadState* ts, JSFrameCallback* th) { } NativeValue* TEST_invokeModule(void* callbackContext, - int32_t contextId, + double contextId, SharedNativeString* moduleName, SharedNativeString* method, SharedNativeString* params, @@ -60,12 +60,12 @@ NativeValue* TEST_invokeModule(void* callbackContext, std::string module = nativeStringToStdString(moduleName); if (module == "throwError") { - callback(callbackContext, contextId, nativeStringToStdString(method).c_str(), nullptr); + callback(callbackContext, contextId, nativeStringToStdString(method).c_str(), nullptr, nullptr, nullptr); } if (module == "MethodChannel") { NativeValue data = Native_NewCString("{\"result\": 1234}"); - callback(callbackContext, contextId, nullptr, &data); + callback(callbackContext, contextId, nullptr, &data, nullptr, nullptr); } auto* result = static_cast(malloc(sizeof(NativeValue))); @@ -74,13 +74,15 @@ NativeValue* TEST_invokeModule(void* callbackContext, return result; }; -void TEST_requestBatchUpdate(int32_t contextId){}; +void TEST_requestBatchUpdate(double contextId){}; -void TEST_reloadApp(int32_t contextId) {} +void TEST_reloadApp(double contextId) {} -int32_t timerId = 0; - -int32_t TEST_setTimeout(webf::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { +void TEST_setTimeout(int32_t new_timer_id, + webf::DOMTimer* timer, + double contextId, + AsyncCallback callback, + int32_t timeout) { auto* context = timer->context(); JSRuntime* rt = context->dartIsolateContext()->runtime(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); @@ -92,14 +94,15 @@ int32_t TEST_setTimeout(webf::DOMTimer* timer, int32_t contextId, AsyncCallback th->timer = timer; th->contextId = contextId; th->isInterval = false; - int32_t id = timerId++; - - ts->os_timers[id] = th; - return id; + ts->os_timers[new_timer_id] = th; } -int32_t TEST_setInterval(webf::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { +void TEST_setInterval(int32_t new_timer_id, + webf::DOMTimer* timer, + double contextId, + AsyncCallback callback, + int32_t timeout) { auto* context = timer->context(); JSRuntime* rt = context->dartIsolateContext()->runtime(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); @@ -111,16 +114,16 @@ int32_t TEST_setInterval(webf::DOMTimer* timer, int32_t contextId, AsyncCallback th->timer = timer; th->contextId = contextId; th->isInterval = true; - int32_t id = timerId++; - - ts->os_timers[id] = th; - return id; + ts->os_timers[new_timer_id] = th; } int32_t callbackId = 0; -uint32_t TEST_requestAnimationFrame(webf::FrameCallback* frameCallback, int32_t contextId, AsyncRAFCallback handler) { +void TEST_requestAnimationFrame(int32_t new_id, + webf::FrameCallback* frameCallback, + double contextId, + AsyncRAFCallback handler) { auto* context = frameCallback->context(); JSRuntime* rt = context->dartIsolateContext()->runtime(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); @@ -128,43 +131,39 @@ uint32_t TEST_requestAnimationFrame(webf::FrameCallback* frameCallback, int32_t th->handler = handler; th->callback = frameCallback; th->contextId = context->contextId(); - int32_t id = callbackId++; - - th->callbackId = id; - - ts->os_frameCallbacks[id] = th; + th->callbackId = new_id; - return id; + ts->os_frameCallbacks[new_id] = th; } -void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { +void TEST_cancelAnimationFrame(double contextId, int32_t id) { auto* page = test_context_map[contextId]->page(); - auto* context = page->GetExecutingContext(); + auto* context = page->executingContext(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->dartIsolateContext()->runtime())); ts->os_frameCallbacks.erase(id); } -void TEST_clearTimeout(int32_t contextId, int32_t timerId) { +void TEST_clearTimeout(double contextId, int32_t timerId) { auto* page = test_context_map[contextId]->page(); - auto* context = page->GetExecutingContext(); + auto* context = page->executingContext(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->dartIsolateContext()->runtime())); ts->os_timers.erase(timerId); } -NativeScreen* TEST_getScreen(int32_t contextId) { +NativeScreen* TEST_getScreen(double contextId) { return nullptr; }; -double TEST_devicePixelRatio(int32_t contextId) { +double TEST_devicePixelRatio(double contextId) { return 1.0; } -SharedNativeString* TEST_platformBrightness(int32_t contextId) { +SharedNativeString* TEST_platformBrightness(double contextId) { return nullptr; } void TEST_toBlob(void* ptr, - int32_t contextId, + double contextId, AsyncBlobCallback blobCallback, int32_t elementId, double devicePixelRatio) { @@ -172,16 +171,16 @@ void TEST_toBlob(void* ptr, blobCallback(ptr, contextId, nullptr, bytes, 5); } -void TEST_flushUICommand(int32_t contextId) { +void TEST_flushUICommand(double contextId) { auto* page = test_context_map[contextId]->page(); clearUICommandItems(reinterpret_cast(page)); } -void TEST_CreateBindingObject(int32_t context_id, void* native_binding_object, int32_t type, void* args, int32_t argc) { +void TEST_CreateBindingObject(double context_id, void* native_binding_object, int32_t type, void* args, int32_t argc) {} -} +void TEST_GetWidgetElementShape() {} -void TEST_onJsLog(int32_t contextId, int32_t level, const char*) {} +void TEST_onJsLog(double contextId, int32_t level, const char*) {} #if ENABLE_PROFILE NativePerformanceEntryList* TEST_getPerformanceEntries(int32_t) { @@ -190,7 +189,7 @@ NativePerformanceEntryList* TEST_getPerformanceEntries(int32_t) { #endif std::once_flag testInitOnceFlag; -int32_t contextId = 0; +double contextId = -1; WebFTestEnv::WebFTestEnv(DartIsolateContext* owner_isolate_context, webf::WebFPage* page) : page_(page), isolate_context_(owner_isolate_context) {} @@ -201,17 +200,16 @@ WebFTestEnv::~WebFTestEnv() { std::unique_ptr TEST_init(OnJSError onJsError) { auto mockedDartMethods = TEST_getMockDartMethods(onJsError); - auto* dart_isolate_context = initDartIsolateContext(mockedDartMethods.data(), mockedDartMethods.size()); - int pageContextId = contextId++; - auto* page = allocateNewPage(dart_isolate_context, pageContextId); + auto* dart_isolate_context = initDartIsolateContextSync(0, mockedDartMethods.data(), mockedDartMethods.size()); + double pageContextId = contextId -= 1; + auto* page = allocateNewPageSync(pageContextId, dart_isolate_context); void* testContext = initTestFramework(page); test_context_map[pageContextId] = reinterpret_cast(testContext); TEST_mockTestEnvDartMethods(testContext, onJsError); JS_TurnOnGC(static_cast(dart_isolate_context)->runtime()); JSThreadState* th = new JSThreadState(); JS_SetRuntimeOpaque( - reinterpret_cast(testContext)->page()->GetExecutingContext()->dartIsolateContext()->runtime(), - th); + reinterpret_cast(testContext)->page()->executingContext()->dartIsolateContext()->runtime(), th); return std::make_unique((webf::DartIsolateContext*)dart_isolate_context, (webf::WebFPage*)page); } @@ -222,9 +220,9 @@ std::unique_ptr TEST_init() { std::unique_ptr TEST_allocateNewPage(OnJSError onJsError) { auto mockedDartMethods = TEST_getMockDartMethods(onJsError); auto dart_isolate_context = std::unique_ptr( - (DartIsolateContext*)initDartIsolateContext(mockedDartMethods.data(), mockedDartMethods.size())); - int pageContextId = contextId++; - auto* page = allocateNewPage(dart_isolate_context.get(), pageContextId); + (DartIsolateContext*)initDartIsolateContextSync(0, mockedDartMethods.data(), mockedDartMethods.size())); + int pageContextId = contextId -= 1; + auto* page = allocateNewPageSync(pageContextId, dart_isolate_context.get()); void* testContext = initTestFramework(page); test_context_map[pageContextId] = reinterpret_cast(testContext); @@ -287,9 +285,9 @@ void TEST_runLoop(webf::ExecutingContext* context) { } } -void TEST_onJSLog(int32_t contextId, int32_t level, const char*) {} +void TEST_onJSLog(double contextId, int32_t level, const char*) {} void TEST_onMatchImageSnapshot(void* callbackContext, - int32_t contextId, + double contextId, uint8_t* bytes, int32_t length, SharedNativeString* name, @@ -298,7 +296,7 @@ void TEST_onMatchImageSnapshot(void* callbackContext, } void TEST_onMatchImageSnapshotBytes(void* callback_context, - int32_t context_id, + double context_id, uint8_t* image_a_bytes, int32_t image_a_size, uint8_t* image_b_bytes, @@ -326,13 +324,8 @@ std::vector TEST_getMockDartMethods(OnJSError onJSError) { reinterpret_cast(TEST_cancelAnimationFrame), reinterpret_cast(TEST_toBlob), reinterpret_cast(TEST_flushUICommand), - reinterpret_cast(TEST_CreateBindingObject)}; - -#if ENABLE_PROFILE - mockMethods.emplace_back(reinterpret_cast(TEST_getPerformanceEntries)); -#else - mockMethods.emplace_back(0); -#endif + reinterpret_cast(TEST_CreateBindingObject), + reinterpret_cast(TEST_GetWidgetElementShape)}; mockMethods.emplace_back(reinterpret_cast(onJSError)); mockMethods.emplace_back(reinterpret_cast(TEST_onJsLog)); diff --git a/bridge/webf_bridge.cc b/bridge/webf_bridge.cc index da5267e981..9250e6f07b 100644 --- a/bridge/webf_bridge.cc +++ b/bridge/webf_bridge.cc @@ -1,19 +1,15 @@ /* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#include -#include -#include - -#include "bindings/qjs/native_string_utils.h" +#include "include/webf_bridge.h" +#include "core/api/api.h" #include "core/dart_isolate_context.h" #include "core/html/parser/html_parser.h" #include "core/page.h" -#include "foundation/logging.h" -#include "foundation/ui_command_buffer.h" -#include "include/webf_bridge.h" +#include "include/dart_api.h" +#include "multiple_threading/dispatcher.h" +#include "multiple_threading/task.h" #if defined(_WIN32) #define SYSTEM_NAME "windows" // Windows @@ -38,75 +34,141 @@ #define SYSTEM_NAME "unknown" #endif -static std::atomic unique_page_id{0}; +static std::atomic unique_page_id{1}; -void* initDartIsolateContext(uint64_t* dart_methods, int32_t dart_methods_len) { - void* ptr = new webf::DartIsolateContext(dart_methods, dart_methods_len); - return ptr; +int64_t newPageIdSync() { + return unique_page_id++; } -void* allocateNewPage(void* dart_isolate_context, int32_t targetContextId) { +void* initDartIsolateContextSync(int64_t dart_port, uint64_t* dart_methods, int32_t dart_methods_len) { + auto dispatcher = std::make_unique(dart_port); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: initDartIsolateContextSync Call BEGIN"; +#endif + auto* dart_isolate_context = new webf::DartIsolateContext(dart_methods, dart_methods_len); + dart_isolate_context->SetDispatcher(std::move(dispatcher)); + +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: initDartIsolateContextSync Call END"; +#endif + + return dart_isolate_context; +} + +void* allocateNewPageSync(double thread_identity, void* ptr) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: allocateNewPageSync Call BEGIN"; +#endif + auto* dart_isolate_context = (webf::DartIsolateContext*)ptr; assert(dart_isolate_context != nullptr); - auto page = - std::make_unique((webf::DartIsolateContext*)dart_isolate_context, targetContextId, nullptr); - void* ptr = page.get(); - ((webf::DartIsolateContext*)dart_isolate_context)->AddNewPage(std::move(page)); - return ptr; + void* result = static_cast(dart_isolate_context)->AddNewPageSync(thread_identity); +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: allocateNewPageSync Call END"; +#endif + return result; } -int64_t newPageId() { - return unique_page_id++; +void allocateNewPage(double thread_identity, + void* ptr, + Dart_Handle dart_handle, + AllocateNewPageCallback result_callback) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: allocateNewPage Call BEGIN"; +#endif + auto* dart_isolate_context = (webf::DartIsolateContext*)ptr; + assert(dart_isolate_context != nullptr); + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + + static_cast(dart_isolate_context) + ->AddNewPage(thread_identity, persistent_handle, result_callback); +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: allocateNewPage Call END"; +#endif } -void disposePage(void* dart_isolate_context, void* page_) { - auto* page = reinterpret_cast(page_); - assert(std::this_thread::get_id() == page->currentThread()); - ((webf::DartIsolateContext*)dart_isolate_context)->RemovePage(page); +void disposePage(double thread_identity, + void* ptr, + void* page_, + Dart_Handle dart_handle, + DisposePageCallback result_callback) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: disposePage Call BEGIN"; +#endif + + WEBF_LOG(VERBOSE) << " DISPOSE PAGE START"; + auto* dart_isolate_context = (webf::DartIsolateContext*)ptr; + + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + + ((webf::DartIsolateContext*)dart_isolate_context) + ->RemovePage(thread_identity, static_cast(page_), persistent_handle, result_callback); +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: disposePage Call END"; +#endif } -int8_t evaluateScripts(void* page_, - const char* code, - uint64_t code_len, - uint8_t** parsed_bytecodes, - uint64_t* bytecode_len, - const char* bundleFilename, - int32_t startLine) { - auto page = reinterpret_cast(page_); - assert(std::this_thread::get_id() == page->currentThread()); - return page->evaluateScript(code, code_len, parsed_bytecodes, bytecode_len, bundleFilename, startLine) ? 1 : 0; +void disposePageSync(double thread_identity, void* ptr, void* page_) { +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: disposePageSync Call BEGIN"; +#endif + auto* dart_isolate_context = (webf::DartIsolateContext*)ptr; + ((webf::DartIsolateContext*)dart_isolate_context) + ->RemovePageSync(thread_identity, static_cast(page_)); +#if ENABLE_LOG + WEBF_LOG(INFO) << "[Dispatcher]: disposePageSync Call END"; +#endif } -int8_t evaluateQuickjsByteCode(void* page_, uint8_t* bytes, int32_t byteLen) { +void evaluateScripts(void* page_, + const char* code, + uint64_t code_len, + uint8_t** parsed_bytecodes, + uint64_t* bytecode_len, + const char* bundleFilename, + int32_t startLine, + Dart_Handle dart_handle, + EvaluateScriptsCallback result_callback) { +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dart] evaluateScriptsWrapper call" << std::endl; +#endif auto page = reinterpret_cast(page_); - assert(std::this_thread::get_id() == page->currentThread()); - return page->evaluateByteCode(bytes, byteLen) ? 1 : 0; + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + page->executingContext()->dartIsolateContext()->dispatcher()->PostToJs( + page->isDedicated(), page->contextId(), webf::evaluateScriptsInternal, page_, code, code_len, parsed_bytecodes, + bytecode_len, bundleFilename, startLine, persistent_handle, result_callback); } -void parseHTML(void* page_, const char* code, int32_t length) { +void evaluateQuickjsByteCode(void* page_, + uint8_t* bytes, + int32_t byteLen, + Dart_Handle dart_handle, + EvaluateQuickjsByteCodeCallback result_callback) { +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dart] evaluateQuickjsByteCodeWrapper call" << std::endl; +#endif auto page = reinterpret_cast(page_); - assert(std::this_thread::get_id() == page->currentThread()); - page->parseHTML(code, length); + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + page->dartIsolateContext()->dispatcher()->PostToJs(page->isDedicated(), page->contextId(), + webf::evaluateQuickjsByteCodeInternal, page_, bytes, byteLen, + persistent_handle, result_callback); } -void* parseSVGResult(const char* code, int32_t length) { - auto* result = webf::HTMLParser::parseSVGResult(code, length); - return result; +void parseHTML(void* page_, const char* code, int32_t length) { +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dart] parseHTMLWrapper call" << std::endl; +#endif + auto page = reinterpret_cast(page_); + page->executingContext()->dartIsolateContext()->dispatcher()->PostToJs(page->isDedicated(), page->contextId(), + webf::parseHTMLInternal, page_, code, length); } -void freeSVGResult(void* svgTree) { - webf::HTMLParser::freeSVGResult(reinterpret_cast(svgTree)); +void registerPluginByteCode(uint8_t* bytes, int32_t length, const char* pluginName) { + webf::ExecutingContext::plugin_byte_code[pluginName] = webf::NativeByteCode{bytes, length}; } -NativeValue* invokeModuleEvent(void* page_, - SharedNativeString* module_name, - const char* eventType, - void* event, - NativeValue* extra) { - auto page = reinterpret_cast(page_); - assert(std::this_thread::get_id() == page->currentThread()); - auto* result = page->invokeModuleEvent(reinterpret_cast(module_name), eventType, event, - reinterpret_cast(extra)); - return reinterpret_cast(result); +void registerPluginCode(const char* code, int32_t length, const char* pluginName) { + webf::ExecutingContext::plugin_string_code[pluginName] = std::string(code, length); } static WebFInfo* webfInfo{nullptr}; @@ -123,50 +185,78 @@ WebFInfo* getWebFInfo() { return webfInfo; } +void* parseSVGResult(const char* code, int32_t length) { + auto* result = webf::HTMLParser::parseSVGResult(code, length); + return result; +} + +void freeSVGResult(void* svgTree) { + webf::HTMLParser::freeSVGResult(reinterpret_cast(svgTree)); +} + +void invokeModuleEvent(void* page_, + SharedNativeString* module, + const char* eventType, + void* event, + NativeValue* extra, + Dart_Handle dart_handle, + InvokeModuleEventCallback result_callback) { + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + auto page = reinterpret_cast(page_); + auto dart_isolate_context = page->executingContext()->dartIsolateContext(); + auto is_dedicated = page->executingContext()->isDedicated(); + auto context_id = page->contextId(); + dart_isolate_context->dispatcher()->PostToJs(is_dedicated, context_id, webf::invokeModuleEventInternal, page_, module, + eventType, event, extra, persistent_handle, result_callback); +} + void dispatchUITask(void* page_, void* context, void* callback) { auto page = reinterpret_cast(page_); - assert(std::this_thread::get_id() == page->currentThread()); reinterpret_cast(callback)(context); } void* getUICommandItems(void* page_) { auto page = reinterpret_cast(page_); - assert(std::this_thread::get_id() == page->currentThread()); - return page->GetExecutingContext()->uiCommandBuffer()->data(); + return page->executingContext()->uiCommandBuffer()->data(); } -int64_t getUICommandItemSize(void* page_) { +uint32_t getUICommandKindFlag(void* page_) { auto page = reinterpret_cast(page_); - assert(std::this_thread::get_id() == page->currentThread()); - return page->GetExecutingContext()->uiCommandBuffer()->size(); + return page->executingContext()->uiCommandBuffer()->kindFlag(); } -void clearUICommandItems(void* page_) { +void acquireUiCommandLocks(void* page_) { auto page = reinterpret_cast(page_); - assert(std::this_thread::get_id() == page->currentThread()); - page->GetExecutingContext()->uiCommandBuffer()->clear(); + page->executingContext()->uiCommandBuffer()->acquireLocks(); } -void registerPluginByteCode(uint8_t* bytes, int32_t length, const char* pluginName) { - webf::ExecutingContext::plugin_byte_code[pluginName] = webf::NativeByteCode{bytes, length}; +void releaseUiCommandLocks(void* page_) { + auto page = reinterpret_cast(page_); + page->executingContext()->uiCommandBuffer()->releaseLocks(); } -void registerPluginCode(const char* code, int32_t length, const char* pluginName) { - webf::ExecutingContext::plugin_string_code[pluginName] = std::string(code, length); +int64_t getUICommandItemSize(void* page_) { + auto page = reinterpret_cast(page_); + return page->executingContext()->uiCommandBuffer()->size(); } -int32_t profileModeEnabled() { -#if ENABLE_PROFILE - return 1; -#else - return 0; -#endif +void clearUICommandItems(void* page_) { + auto page = reinterpret_cast(page_); + page->executingContext()->uiCommandBuffer()->clear(); } // Callbacks when dart context object was finalized by Dart GC. static void finalize_dart_context(void* isolate_callback_data, void* peer) { +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: BEGIN FINALIZE DART CONTEXT: "; +#endif auto* dart_isolate_context = (webf::DartIsolateContext*)peer; - delete dart_isolate_context; + dart_isolate_context->Dispose([dart_isolate_context]() { + free(dart_isolate_context); +#if ENABLE_LOG + WEBF_LOG(VERBOSE) << "[Dispatcher]: SUCCESS FINALIZE DART CONTEXT"; +#endif + }); } void init_dart_dynamic_linking(void* data) { @@ -179,3 +269,16 @@ void register_dart_context_finalizer(Dart_Handle dart_handle, void* dart_isolate Dart_NewFinalizableHandle_DL(dart_handle, reinterpret_cast(dart_isolate_context), sizeof(webf::DartIsolateContext), finalize_dart_context); } + +int8_t isJSThreadBlocked(void* dart_isolate_context_, double context_id) { + auto* dart_isolate_context = static_cast(dart_isolate_context_); + auto thread_group_id = static_cast(context_id); + return dart_isolate_context->dispatcher()->IsThreadBlocked(thread_group_id) ? 1 : 0; +} + +// run in the dart isolate thread +void executeNativeCallback(DartWork* work_ptr) { + auto dart_work = *(work_ptr); + dart_work(false); + delete work_ptr; +} \ No newline at end of file diff --git a/bridge/webf_bridge_test.cc b/bridge/webf_bridge_test.cc index 0af340b79a..42e1b7d8da 100644 --- a/bridge/webf_bridge_test.cc +++ b/bridge/webf_bridge_test.cc @@ -31,12 +31,21 @@ void* initTestFramework(void* page_) { signal(SIGSEGV, handler); // install handler when crashed. signal(SIGABRT, handler); auto page = reinterpret_cast(page_); - assert(std::this_thread::get_id() == page->currentThread()); - return new webf::WebFTestContext(page->GetExecutingContext()); + return page->dartIsolateContext()->dispatcher()->PostToJsSync( + page->isDedicated(), page->contextId(), + [](bool cancel, webf::WebFPage* page) -> void* { return new webf::WebFTestContext(page->executingContext()); }, + page); } -void executeTest(void* testContext, ExecuteCallback executeCallback) { - reinterpret_cast(testContext)->invokeExecuteTest(executeCallback); +void executeTest(void* testContext, Dart_Handle dart_handle, ExecuteResultCallback executeCallback) { + auto context = reinterpret_cast(testContext); + Dart_PersistentHandle persistent_handle = Dart_NewPersistentHandle_DL(dart_handle); + + context->page()->dartIsolateContext()->dispatcher()->PostToJs( + context->page()->isDedicated(), context->page()->contextId(), + [](webf::WebFTestContext* context, Dart_PersistentHandle persistent_handle, + ExecuteResultCallback executeCallback) { context->invokeExecuteTest(persistent_handle, executeCallback); }, + context, persistent_handle, executeCallback); } void registerTestEnvDartMethods(void* testContext, uint64_t* methodBytes, int32_t length) { diff --git a/integration_tests/lib/bridge/from_native.dart b/integration_tests/lib/bridge/from_native.dart index 27f54e1f5a..9589e2ba07 100644 --- a/integration_tests/lib/bridge/from_native.dart +++ b/integration_tests/lib/bridge/from_native.dart @@ -31,33 +31,25 @@ import 'test_input.dart'; // 5. Get a reference to the C function, and put it into a variable. // 6. Call from C. -typedef NativeJSError = Void Function(Int32 contextId, Pointer); +typedef NativeJSError = Void Function(Double contextId, Pointer); typedef JSErrorListener = void Function(String); -List _listenerList = List.filled(10, (String string) { - throw new Exception('unimplemented JS ErrorListener'); -}); - -void addJSErrorListener(int contextId, JSErrorListener listener) { - _listenerList[contextId] = listener; -} - -void _onJSError(int contextId, Pointer charStr) { +void _onJSError(double contextId, Pointer charStr) { String msg = (charStr).toDartString(); - _listenerList[contextId](msg); + print(msg); } final Pointer> _nativeOnJsError = Pointer.fromFunction(_onJSError); typedef NativeMatchImageSnapshotCallback = Void Function( - Pointer callbackContext, Int32 contextId, Int8, Pointer); + Pointer callbackContext, Double contextId, Int8, Pointer); typedef DartMatchImageSnapshotCallback = void Function( - Pointer callbackContext, int contextId, int, Pointer); -typedef NativeMatchImageSnapshot = Void Function(Pointer callbackContext, Int32 contextId, Pointer, Int32, + Pointer callbackContext, double contextId, int, Pointer); +typedef NativeMatchImageSnapshot = Void Function(Pointer callbackContext, Double contextId, Pointer, Int32, Pointer, Pointer>); -typedef NativeMatchImageSnapshotBytes = Void Function(Pointer callbackContext, Int32 contextId, Pointer, Int32, Pointer, Int32, Pointer>); +typedef NativeMatchImageSnapshotBytes = Void Function(Pointer callbackContext, Double contextId, Pointer, Int32, Pointer, Int32, Pointer>); -void _matchImageSnapshot(Pointer callbackContext, int contextId, Pointer bytes, int size, +void _matchImageSnapshot(Pointer callbackContext, double contextId, Pointer bytes, int size, Pointer snapshotNamePtr, Pointer> pointer) { DartMatchImageSnapshotCallback callback = pointer.asFunction(); String filename = nativeStringToString(snapshotNamePtr); @@ -70,7 +62,7 @@ void _matchImageSnapshot(Pointer callbackContext, int contextId, Pointer callbackContext, int contextId, Pointer bytes, int size, +void _matchImageSnapshotBytes(Pointer callbackContext, double contextId, Pointer bytes, int size, Pointer imageBBytes, int imageBSize, Pointer> pointer) { DartMatchImageSnapshotCallback callback = pointer.asFunction(); matchImageSnapshotBytes(bytes.asTypedList(size), imageBBytes.asTypedList(size)).then((value) { @@ -103,8 +95,8 @@ PointerChange _getPointerChange(double change) { } class MousePointer extends Struct { - @Int32() - external int contextId; + @Double() + external double contextId; @Double() external double x; @@ -126,7 +118,7 @@ class MousePointer extends Struct { } void _simulatePointer(Pointer context, Pointer mousePointerList, int length, int pointer, Pointer> callback) { - int _contextId = 0; + double _contextId = 0; sendPointerToWindow(List> data, int index) { if (index >= data.length) { DartAsyncCallback fn = callback.asFunction(); @@ -179,6 +171,7 @@ void _simulatePointer(Pointer context, Pointer mousePointerL } sendPointerToWindow(dataList, 0); + malloc.free(mousePointerList); } final Pointer> _nativeSimulatePointer = Pointer.fromFunction(_simulatePointer); @@ -210,7 +203,7 @@ final Dart_RegisterTestEnvDartMethods _registerTestEnvDartMethods = WebFDynamicL .lookup>('registerTestEnvDartMethods') .asFunction(); -void registerDartTestMethodsToCpp(int contextId) { +void registerDartTestMethodsToCpp(double contextId) { Pointer bytes = malloc.allocate(sizeOf() * _dartNativeMethods.length); Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); diff --git a/integration_tests/lib/bridge/to_native.dart b/integration_tests/lib/bridge/to_native.dart index 1b3a5dd99a..335c73b1b5 100644 --- a/integration_tests/lib/bridge/to_native.dart +++ b/integration_tests/lib/bridge/to_native.dart @@ -23,41 +23,47 @@ typedef DartInitTestFramework = Pointer Function(Pointer page); final DartInitTestFramework _initTestFramework = WebFDynamicLibrary.testRef.lookup>('initTestFramework').asFunction(); -Pointer initTestFramework(int contextId) { +Pointer initTestFramework(double contextId) { return _initTestFramework(getAllocatedPage(contextId)!); } // Register evaluteTestScripts -typedef NativeEvaluateTestScripts = Int8 Function(Pointer testContext, Pointer, Pointer, Int32); +typedef NativeEvaluateTestScripts = Int8 Function( + Pointer testContext, Pointer, Pointer, Int32); typedef DartEvaluateTestScripts = int Function(Pointer testContext, Pointer, Pointer, int); final DartEvaluateTestScripts _evaluateTestScripts = WebFDynamicLibrary.testRef.lookup>('evaluateTestScripts').asFunction(); -void evaluateTestScripts(int contextId, String code, {String url = 'test://', int line = 0}) { +void evaluateTestScripts(double contextId, String code, {String url = 'test://', int line = 0}) { Pointer _url = (url).toNativeUtf8(); _evaluateTestScripts(getAllocatedPage(contextId)!, stringToNativeString(code), _url, line); } -typedef NativeExecuteCallback = Void Function(Int32 contextId, Pointer status); -typedef DartExecuteCallback = void Function(int); -typedef NativeExecuteTest = Void Function(Pointer, Pointer>); -typedef DartExecuteTest = void Function(Pointer, Pointer>); +typedef ExecuteCallbackResultCallback = Void Function(Handle context, Pointer result); +typedef DartExecuteCallback = void Function(double); +typedef NativeExecuteTest = Void Function(Pointer, Handle context, + Pointer> resultCallback); +typedef DartExecuteTest = void Function(Pointer, Object context, + Pointer> resultCallback); final DartExecuteTest _executeTest = WebFDynamicLibrary.testRef.lookup>('executeTest').asFunction(); -List?> completerList = List.filled(10, null); +class _ExecuteTestContext { + Completer completer; + _ExecuteTestContext(this.completer); +} -void _executeTestCallback(int contextId, Pointer status) { - if (completerList[contextId] == null) return; - completerList[contextId]!.complete(nativeStringToString(status)); - completerList[contextId] = null; +void _handleExecuteTestResult(_ExecuteTestContext context, Pointer resultData) { + String status = nativeStringToString(resultData); + context.completer.complete(status); } -Future executeTest(Pointer testContext, int contextId) async { - completerList[contextId] = Completer(); - Pointer> callback = Pointer.fromFunction(_executeTestCallback); - _executeTest(testContext, callback); - return completerList[contextId]!.future; +Future executeTest(Pointer testContext, double contextId) async { + Completer completer = Completer(); + _ExecuteTestContext context = _ExecuteTestContext(completer); + Pointer> callback = Pointer.fromFunction(_handleExecuteTestResult); + _executeTest(testContext, context, callback); + return completer.future; } diff --git a/integration_tests/lib/webf_tester.dart b/integration_tests/lib/webf_tester.dart index fefa5ad2e4..f6c7e06fb0 100644 --- a/integration_tests/lib/webf_tester.dart +++ b/integration_tests/lib/webf_tester.dart @@ -64,6 +64,7 @@ class _WebFTesterState extends State { javaScriptChannel: javaScriptChannel, onControllerCreated: onControllerCreated, onLoad: onLoad, + // runningThread: FlutterUIThread(), gestureListener: GestureListener( onDrag: (GestureEvent gestureEvent) { if (gestureEvent.state == EVENT_STATE_START) { @@ -77,11 +78,10 @@ class _WebFTesterState extends State { onControllerCreated(WebFController controller) async { this.controller = controller; - int contextId = controller.view.contextId; + double contextId = controller.view.contextId; testContext = initTestFramework(contextId); registerDartTestMethodsToCpp(contextId); - addJSErrorListener(contextId, print); - controller.view.evaluateJavaScripts(widget.preCode); + await controller.view.evaluateJavaScripts(widget.preCode); } onLoad(WebFController controller) async { @@ -91,19 +91,23 @@ class _WebFTesterState extends State { mems.add([x += 1, ProcessInfo.currentRss / 1024 ~/ 1024]); }); - // Preload load test cases - String result = await executeTest(testContext!, controller.view.contextId); - // Manual dispose context for memory leak check. - controller.dispose(); + try { + // Preload load test cases + String result = await executeTest(testContext!, controller.view.contextId); + // Manual dispose context for memory leak check. + await controller.dispose(); - // Check running memorys - // Temporary disabled due to exist memory leaks - // if (isMemLeaks(mems)) { - // print('Memory leaks found. ${mems.map((e) => e[1]).toList()}'); - // exit(1); - // } - widget.onWillFinish?.call(); + // Check running memorys + // Temporary disabled due to exist memory leaks + // if (isMemLeaks(mems)) { + // print('Memory leaks found. ${mems.map((e) => e[1]).toList()}'); + // exit(1); + // } + widget.onWillFinish?.call(); - exit(result == 'failed' ? 1 : 0); + exit(result == 'failed' ? 1 : 0); + } catch (e) { + print(e); + } } } diff --git a/integration_tests/macos/Runner.xcodeproj/project.pbxproj b/integration_tests/macos/Runner.xcodeproj/project.pbxproj index 5e98bcdd73..b445de9e87 100644 --- a/integration_tests/macos/Runner.xcodeproj/project.pbxproj +++ b/integration_tests/macos/Runner.xcodeproj/project.pbxproj @@ -206,7 +206,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { diff --git a/integration_tests/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/integration_tests/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 6877ac6b14..b1cfbd3576 100644 --- a/integration_tests/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/integration_tests/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ { console.log(`${data && data.toString().trim()}`); }); - + tester.stderr.on('data', (data) => { console.error(`${data && data.toString().trim()}`); }); @@ -72,3 +73,4 @@ function startIntegrationTest() { } startIntegrationTest(); +startWsServer(8399); diff --git a/integration_tests/specs/dom/elements/custom-element.ts b/integration_tests/specs/dom/elements/custom-element.ts index 808f4a12cf..f2c6fb6495 100644 --- a/integration_tests/specs/dom/elements/custom-element.ts +++ b/integration_tests/specs/dom/elements/custom-element.ts @@ -455,7 +455,7 @@ describe('custom html element', () => { it('dart implements getAllBindingPropertyNames works', async () => { let sampleElement = document.createElement('sample-element'); let attributes = Object.keys(sampleElement); - expect(attributes).toEqual(['classList', 'className', 'clientHeight', 'clientLeft', 'clientTop', 'clientWidth', 'dir', 'fake', 'offsetHeight', 'offsetLeft', 'offsetTop', 'offsetWidth', 'ping', 'scrollHeight', 'scrollLeft', 'scrollTop', 'scrollWidth', 'asyncFn', 'asyncFnFailed', 'asyncFnNotComplete', 'click', 'closest', 'fn', 'getBoundingClientRect', 'getClientRects', 'getElementsByClassName', 'getElementsByTagName', 'matches', 'querySelector', 'querySelectorAll', 'scroll', 'scrollBy', 'scrollTo']); + expect(attributes).toEqual(['offsetTop', 'offsetLeft', 'offsetWidth', 'offsetHeight', 'scrollTop', 'scrollLeft', 'scrollWidth', 'scrollHeight', 'clientTop', 'clientLeft', 'clientWidth', 'clientHeight', 'className', 'classList', 'dir', 'ping', 'fake', 'getBoundingClientRect', 'getClientRects', 'scroll', 'scrollBy', 'scrollTo', 'click', 'getElementsByClassName', 'getElementsByTagName', 'querySelectorAll', 'querySelector', 'matches', 'closest', 'fn', 'asyncFn', 'asyncFnFailed', 'asyncFnNotComplete']); }); it('support custom properties in dart directly', () => { diff --git a/integration_tests/specs/dom/events/event.ts b/integration_tests/specs/dom/events/event.ts index 2a2ec2a14b..93397b337c 100644 --- a/integration_tests/specs/dom/events/event.ts +++ b/integration_tests/specs/dom/events/event.ts @@ -426,7 +426,7 @@ describe('Event', () => { expect(e.type).toBe(type); }); - it('Event Level 0 removal', () => { + it('Event Level 0 removal', async () => { var el = createElement('div', { style: { width: '100px', @@ -443,17 +443,20 @@ describe('Event', () => { } el.onclick = fn1; el.click(); + await sleep(0.1); el.onclick = null; el.click(); + await sleep(0.1); el.onclick = fn2; el.click(); + await sleep(0.1); expect(ret).toEqual('12'); }); - it('Event Level 2 listen multi-times', () => { + it('Event Level 2 listen multi-times', async () => { var el = createElement('div', { style: { width: '100px', @@ -473,10 +476,12 @@ describe('Event', () => { el.addEventListener('click', fn2); el.click(); + await sleep(0.1); + expect(ret).toEqual('12'); }); - it('Event Level 2 listen multi-times with removal', () => { + it('Event Level 2 listen multi-times with removal', async () => { var el = createElement('div', { style: { width: '100px', @@ -499,10 +504,12 @@ describe('Event', () => { el.removeEventListener('click', fn2); el.click(); + await sleep(0.1); + expect(ret).toEqual(''); }); - it('Add multi event types', () => { + it('Add multi event types', async () => { var el = createElement('div', { style: { width: '100px', @@ -519,9 +526,10 @@ describe('Event', () => { el.click(); + await sleep(0.1); expect(ret).toEqual('1'); }); - it('should work with undefined addEventListener options', () => { + it('should work with undefined addEventListener options', async () => { var el = createElement('div', { style: { width: '100px', @@ -537,6 +545,7 @@ describe('Event', () => { el.addEventListener('scroll', fn1, undefined); el.click(); + await sleep(0.1); expect(ret).toEqual('1'); }); diff --git a/integration_tests/specs/dom/events/mouseEvent.ts b/integration_tests/specs/dom/events/mouseEvent.ts index 94d9a91cbc..43813f7886 100644 --- a/integration_tests/specs/dom/events/mouseEvent.ts +++ b/integration_tests/specs/dom/events/mouseEvent.ts @@ -246,7 +246,7 @@ describe('MouseEvent', () => { await simulateClick(10.0, 10.0, 1); }); - it('should work with fixed node which be scolled', async (done) => { + it('should work with fixed node which be scolled', async () => { const div = document.createElement('div') document.body.appendChild(div) div.style.width = '100%'; @@ -255,7 +255,6 @@ describe('MouseEvent', () => { div.appendChild(document.createTextNode('aaa')); - const mask = document.createElement('div'); mask.style.width = '100%'; mask.style.height = '100vh'; @@ -267,10 +266,12 @@ describe('MouseEvent', () => { window.scrollTo(0, '100vh'); + let clicked = false; mask.addEventListener('click', function handleClick() { mask.removeEventListener('click', handleClick); - done(); - }) + clicked = true; + }); await simulateClick(10.0, 10.0, 0); + expect(clicked).toBe(true); }); }); diff --git a/integration_tests/specs/dom/nodes/event-target.ts b/integration_tests/specs/dom/nodes/event-target.ts index f1b0ab17e6..367d20d8e2 100644 --- a/integration_tests/specs/dom/nodes/event-target.ts +++ b/integration_tests/specs/dom/nodes/event-target.ts @@ -18,8 +18,13 @@ describe('DOM EventTarget', () => { div.click(); div.click(); + await sleep(0.1); + div.removeEventListener('click', clickHandler); + + await sleep(0.1); div.click(); + await sleep(0.1); // Only 2 times recorded. expect(clickTime).toBe(2); @@ -34,15 +39,16 @@ describe('DOM EventTarget', () => { div.click(); }); - it('addEventListener should work without connected into element tree', done => { + it('addEventListener should work without connected into element tree', async done => { let div = createElementWithStyle('div', {}); div.addEventListener('click', () => { done(); }); div.click(); + await snapshot(0.1); }); - it('addEventListener should work with multi event handler', done => { + it('addEventListener should work with multi event handler', async done => { let count = 0; let div1 = createElementWithStyle('div', {}); let div2 = createElementWithStyle('div', {}); @@ -61,9 +67,10 @@ describe('DOM EventTarget', () => { BODY.appendChild(div2); div1.click(); div2.click(); + await sleep(0.1); }); - it('addEventListener should work with removeEventListeners', () => { + it('addEventListener should work with removeEventListeners', async () => { let div = createElementWithStyle('div', {}); let count = 0; function onClick() { @@ -76,20 +83,22 @@ describe('DOM EventTarget', () => { div.click(); div.click(); div.click(); + await sleep(0.1); div.addEventListener('click', onClick); expect(count).toBe(1); }); - it('should work with build in property handler', (done) => { + it('should work with build in property handler', async (done) => { let div = createElementWithStyle('div', {}); div.onclick = () => { done(); }; BODY.appendChild(div); div.click(); + await sleep(0.1); }); - it('event object should have type', done => { + it('event object should have type', async done => { let div = createElementWithStyle('div', {}); div.addEventListener('click', (event: any) => { expect(event.type).toBe('click'); @@ -97,9 +106,10 @@ describe('DOM EventTarget', () => { }); BODY.appendChild(div); div.click(); + await sleep(0.1); }); - it('event object target should equal to element itself', done => { + it('event object target should equal to element itself', async done => { let div = createElementWithStyle('div', {}); div.addEventListener('click', (event: any) => { expect(div === event.target); @@ -107,9 +117,10 @@ describe('DOM EventTarget', () => { }); BODY.appendChild(div); div.click(); + await sleep(0.1); }); - it('event object currentTarget should equal to element itself', done => { + it('event object currentTarget should equal to element itself', async done => { let div = createElementWithStyle('div', {}); div.addEventListener('click', (event: any) => { expect(div === event.currentTarget); @@ -117,9 +128,10 @@ describe('DOM EventTarget', () => { }); BODY.appendChild(div); div.click(); + await sleep(0.1); }); - it('trigger twice when onclick and bind addEventListener', () => { + it('trigger twice when onclick and bind addEventListener', async () => { let div = createElementWithStyle('div', {}); let count = 0; div.addEventListener('click', (event: any) => { @@ -130,10 +142,11 @@ describe('DOM EventTarget', () => { } BODY.appendChild(div); div.click(); + await sleep(0.1); expect(count).toBe(2); }); - it('stop propagation', () => { + it('stop propagation', async () => { let count1 = 0, count2 = 0; const div1 = document.createElement('div'); @@ -150,6 +163,7 @@ describe('DOM EventTarget', () => { div2.click(); div2.click(); + await sleep(0.1); expect(count1).toBe(0); expect(count2).toBe(2); @@ -172,7 +186,7 @@ describe('DOM EventTarget', () => { }); - it('removeEventListener should work', (done) => { + it('removeEventListener should work', async (done) => { let num = 0; var ele = createElement('div', { style: { @@ -197,6 +211,7 @@ describe('DOM EventTarget', () => { } ele.click(); + await sleep(0.1); }); }); diff --git a/integration_tests/specs/dom/nodes/mutation-observer.ts b/integration_tests/specs/dom/nodes/mutation-observer.ts index 2c8ff60d33..0487c993cf 100644 --- a/integration_tests/specs/dom/nodes/mutation-observer.ts +++ b/integration_tests/specs/dom/nodes/mutation-observer.ts @@ -476,7 +476,6 @@ describe("MutationObserver microtask looping", function() { // ...which we'll attach to both elements inner!.addEventListener('click', onClick); - outer!.addEventListener('click', onClick); // Note that this will behave differently than a real click, // since the dispatch is synchronous and microtasks will not @@ -487,10 +486,6 @@ describe("MutationObserver microtask looping", function() { 'click', 'promise', 'mutate', - 'click', - 'promise', - 'mutate', - 'timeout', 'timeout' ], 'Level 1 bossfight (synthetic click)'); }); diff --git a/package.json b/package.json index de38a8f7e0..61b4035adc 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "build:bridge:all": "node scripts/prepare_webf_release_binary", "build:bridge:all:release": "WEBF_BUILD=Release node scripts/prepare_webf_release_binary", "build_bridge:all:profile": "ENABLE_PROFILE=true npm run build:bridge:all:release", + "build:clean": "node scripts/clean.js", "pretest": "npm install && ENABLE_ASAN=true node scripts/build_darwin_dylib", "posttest": "npm run lint", "benchmark": "npm install && ENABLE_PROFILE=true WEBF_BUILD=Release node scripts/run_benchmark.js", diff --git a/scripts/build_android_so.js b/scripts/build_android_so.js index 6654a88ced..92eb8a884f 100644 --- a/scripts/build_android_so.js +++ b/scripts/build_android_so.js @@ -8,7 +8,6 @@ const { copyFileSync } = require('fs'); const buildTasks = [ - 'android-so-clean', 'compile-polyfill', 'generate-bindings-code', 'build-android-webf-lib' diff --git a/scripts/build_darwin_dylib.js b/scripts/build_darwin_dylib.js index 46091473ea..4442aa0611 100644 --- a/scripts/build_darwin_dylib.js +++ b/scripts/build_darwin_dylib.js @@ -8,7 +8,6 @@ require('./tasks'); // Run tasks series( - 'macos-dylib-clean', 'compile-polyfill', 'generate-bindings-code', 'build-darwin-webf-lib', diff --git a/scripts/build_ios_framework.js b/scripts/build_ios_framework.js index 10bc487761..45724d5d3b 100644 --- a/scripts/build_ios_framework.js +++ b/scripts/build_ios_framework.js @@ -14,7 +14,6 @@ process.env.PATCH_PROMISE_POLYFILL = 'true'; // Run tasks series( - 'ios-framework-clean', 'compile-polyfill', 'generate-bindings-code', 'build-ios-webf-lib' diff --git a/scripts/build_linux.js b/scripts/build_linux.js index 0855e9c77a..d5bbb564aa 100644 --- a/scripts/build_linux.js +++ b/scripts/build_linux.js @@ -7,7 +7,6 @@ const chalk = require('chalk'); // Run tasks series( - 'clean', 'compile-polyfill', 'generate-bindings-code', 'build-linux-webf-lib' diff --git a/scripts/build_windows_dll.js b/scripts/build_windows_dll.js index 2adb172686..92dec12525 100644 --- a/scripts/build_windows_dll.js +++ b/scripts/build_windows_dll.js @@ -1,20 +1,19 @@ -/** - * Build script for Linux - */ - require('./tasks'); - const { series, parallel } = require('gulp'); - const chalk = require('chalk'); - - // Run tasks - series( - 'clean', - 'compile-polyfill', - 'generate-bindings-code', - 'build-window-webf-lib' - )((err) => { - if (err) { - console.log(err); - } else { - console.log(chalk.green('Success.')); - } - }); \ No newline at end of file +/** + * Build script for Linux + */ + require('./tasks'); + const { series, parallel } = require('gulp'); + const chalk = require('chalk'); + + // Run tasks + series( + 'compile-polyfill', + 'generate-bindings-code', + 'build-window-webf-lib' + )((err) => { + if (err) { + console.log(err); + } else { + console.log(chalk.green('Success.')); + } + }); diff --git a/scripts/clean.js b/scripts/clean.js new file mode 100644 index 0000000000..2fa0905f2d --- /dev/null +++ b/scripts/clean.js @@ -0,0 +1,22 @@ +/** + * Only build libkraken.dylib for macOS + */ +const { series } = require('gulp'); +const chalk = require('chalk'); + +require('./tasks'); + +// Run tasks +series( + 'android-so-clean', + 'ios-framework-clean', + 'macos-dylib-clean', + 'android-so-clean', + 'clean', +)((err) => { + if (err) { + console.log(err); + } else { + console.log(chalk.green('Success.')); + } +}); diff --git a/scripts/tasks.js b/scripts/tasks.js index 686e87b681..7b9d6a66b6 100644 --- a/scripts/tasks.js +++ b/scripts/tasks.js @@ -18,6 +18,7 @@ const uploader = require('./utils/uploader'); program .option('--static-quickjs', 'Build quickjs as static library and bundled into webf library.', false) +.option('--enable-log', 'Enable log printing') .parse(process.argv); const SUPPORTED_JS_ENGINES = ['jsc', 'quickjs']; @@ -106,6 +107,10 @@ task('build-darwin-webf-lib', done => { externCmakeArgs.push('-DSTATIC_QUICKJS=true'); } + if (program.enableLog) { + externCmakeArgs.push('-DENABLE_LOG=true'); + } + execSync(`cmake -DCMAKE_BUILD_TYPE=${buildType} -DENABLE_TEST=true ${externCmakeArgs.join(' ')} \ -G "Unix Makefiles" -B ${paths.bridge}/cmake-build-macos-x86_64 -S ${paths.bridge}`, { cwd: paths.bridge, @@ -354,6 +359,10 @@ task(`build-ios-webf-lib`, (done) => { externCmakeArgs.push('-DUSE_SYSTEM_MALLOC=true'); } + if (program.enableLog) { + externCmakeArgs.push('-DENABLE_LOG=true'); + } + // generate build scripts for simulator execSync(`cmake -DCMAKE_BUILD_TYPE=${buildType} \ -DCMAKE_TOOLCHAIN_FILE=${paths.bridge}/cmake/ios.toolchain.cmake \ @@ -484,6 +493,10 @@ task('build-linux-webf-lib', (done) => { externCmakeArgs.push('-DUSE_SYSTEM_MALLOC=true'); } + if (program.enableLog) { + externCmakeArgs.push('-DENABLE_LOG=true'); + } + const soBinaryDirectory = path.join(paths.bridge, `build/linux/lib/`); const bridgeCmakeDir = path.join(paths.bridge, 'cmake-build-linux'); // generate project @@ -569,6 +582,10 @@ task('build-window-webf-lib', (done) => { externCmakeArgs.push('-DUSE_SYSTEM_MALLOC=true'); } + if (program.enableLog) { + externCmakeArgs.push('-DENABLE_LOG=true'); + } + const soBinaryDirectory = path.join(paths.bridge, `build/windows/lib/`); const bridgeCmakeDir = path.join(paths.bridge, 'cmake-build-windows'); // generate project @@ -635,6 +652,10 @@ task('build-android-webf-lib', (done) => { externCmakeArgs.push('-DENABLE_ASAN=true'); } + if (program.enableLog) { + externCmakeArgs.push('-DENABLE_LOG=true'); + } + if (process.env.USE_SYSTEM_MALLOC === 'true') { externCmakeArgs.push('-DUSE_SYSTEM_MALLOC=true'); } diff --git a/webf/lib/bridge.dart b/webf/lib/bridge.dart index 6587e2cbab..d497b807a6 100644 --- a/webf/lib/bridge.dart +++ b/webf/lib/bridge.dart @@ -11,3 +11,5 @@ export 'src/bridge/from_native.dart'; export 'src/bridge/native_types.dart'; export 'src/bridge/native_value.dart'; export 'src/bridge/native_gumbo.dart'; +export 'src/bridge/ui_command.dart'; +export 'src/bridge/multiple_thread.dart'; diff --git a/webf/lib/foundation.dart b/webf/lib/foundation.dart index 580d60caae..9c21bc564d 100644 --- a/webf/lib/foundation.dart +++ b/webf/lib/foundation.dart @@ -17,3 +17,4 @@ export 'src/foundation/http_overrides.dart'; export 'src/foundation/type.dart'; export 'src/foundation/uri_parser.dart'; export 'src/foundation/bytecode_cache.dart'; +export 'src/foundation/ui_command_iterator.dart'; diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index 40e8a18686..3f755bf585 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -5,6 +5,7 @@ // Bind the JavaScript side object, // provide interface such as property setter/getter, call a property as function. +import 'dart:async'; import 'dart:ffi'; import 'package:ffi/ffi.dart'; @@ -24,9 +25,9 @@ enum BindingMethodCallOperations { } typedef NativeAsyncAnonymousFunctionCallback = Void Function( - Pointer callbackContext, Pointer nativeValue, Int32 contextId, Pointer errmsg); + Pointer callbackContext, Pointer nativeValue, Double contextId, Pointer errmsg); typedef DartAsyncAnonymousFunctionCallback = void Function( - Pointer callbackContext, Pointer nativeValue, int contextId, Pointer errmsg); + Pointer callbackContext, Pointer nativeValue, double contextId, Pointer errmsg); typedef BindingCallFunc = dynamic Function(BindingObject bindingObject, List args); @@ -39,22 +40,72 @@ List bindingCallMethodDispatchTable = [ ]; // Dispatch the event to the binding side. -void _dispatchNomalEventToNative(Event event) { - _dispatchEventToNative(event, false); +Future _dispatchNomalEventToNative(Event event) async { + await _dispatchEventToNative(event, false); } -void _dispatchCaptureEventToNative(Event event) { - _dispatchEventToNative(event, true); +Future _dispatchCaptureEventToNative(Event event) async { + await _dispatchEventToNative(event, true); } -void _dispatchEventToNative(Event event, bool isCapture) { + +void _handleDispatchResult(_DispatchEventResultContext context, Pointer returnValue) { + Pointer dispatchResult = fromNativeValue(context.controller.view, returnValue).cast(); + Event event = context.event; + event.cancelable = dispatchResult.ref.canceled; + event.propagationStopped = dispatchResult.ref.propagationStopped; + event.sharedJSProps = Pointer.fromAddress(context.rawEvent.ref.bytes.elementAt(8).value); + event.propLen = context.rawEvent.ref.bytes.elementAt(9).value; + event.allocateLen = context.rawEvent.ref.bytes.elementAt(10).value; + + if (enableWebFCommandLog && context.stopwatch != null) { + print('dispatch event to native side: target: ${event.target} arguments: ${context.dispatchEventArguments} time: ${context.stopwatch!.elapsedMicroseconds}us'); + } + + // Free the allocated arguments. + malloc.free(context.rawEvent); + malloc.free(context.method); + malloc.free(context.allocatedNativeArguments); + malloc.free(dispatchResult); + malloc.free(returnValue); + + context.completer.complete(); +} + +class _DispatchEventResultContext { + Completer completer; + Stopwatch? stopwatch; + Event event; + Pointer method; + Pointer allocatedNativeArguments; + Pointer rawEvent; + List dispatchEventArguments; + WebFController controller; + _DispatchEventResultContext( + this.completer, + this.event, + this.method, + this.allocatedNativeArguments, + this.rawEvent, + this.controller, + this.dispatchEventArguments, + this.stopwatch + ); +} + +Future _dispatchEventToNative(Event event, bool isCapture) async { Pointer? pointer = event.currentTarget?.pointer; - int? contextId = event.target?.contextId; + double? contextId = event.target?.contextId; WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; + + if (controller.view.disposed) return; + if (contextId != null && pointer != null && pointer.ref.invokeBindingMethodFromDart != nullptr && event.target?.pointer?.ref.disposed != true && event.currentTarget?.pointer?.ref.disposed != true ) { + Completer completer = Completer(); + BindingObject bindingObject = controller.view.getBindingObject(pointer); // Call methods implements at C++ side. DartInvokeBindingMethodsFromDart f = pointer.ref.invokeBindingMethodFromDart.asFunction(); @@ -63,7 +114,7 @@ void _dispatchEventToNative(Event event, bool isCapture) { List dispatchEventArguments = [event.type, rawEvent, isCapture]; Stopwatch? stopwatch; - if (isEnabledLog) { + if (enableWebFCommandLog) { stopwatch = Stopwatch()..start(); } @@ -71,26 +122,24 @@ void _dispatchEventToNative(Event event, bool isCapture) { toNativeValue(method, 'dispatchEvent'); Pointer allocatedNativeArguments = makeNativeValueArguments(bindingObject, dispatchEventArguments); - Pointer returnValue = malloc.allocate(sizeOf()); - f(pointer, returnValue, method, dispatchEventArguments.length, allocatedNativeArguments, event); - Pointer dispatchResult = fromNativeValue(controller.view, returnValue).cast(); - event.cancelable = dispatchResult.ref.canceled; - event.propagationStopped = dispatchResult.ref.propagationStopped; - - event.sharedJSProps = Pointer.fromAddress(rawEvent.ref.bytes.elementAt(8).value); - event.propLen = rawEvent.ref.bytes.elementAt(9).value; - event.allocateLen = rawEvent.ref.bytes.elementAt(10).value; - - if (isEnabledLog) { - print('dispatch event to native side: target: ${event.target} arguments: $dispatchEventArguments time: ${stopwatch!.elapsedMicroseconds}us'); - } + _DispatchEventResultContext context = _DispatchEventResultContext( + completer, + event, + method, + allocatedNativeArguments, + rawEvent, + controller, + dispatchEventArguments, + stopwatch + ); + + Pointer> resultCallback = Pointer.fromFunction(_handleDispatchResult); + + Future.microtask(() { + f(pointer, method, dispatchEventArguments.length, allocatedNativeArguments, context, resultCallback); + }); - // Free the allocated arguments. - malloc.free(rawEvent); - malloc.free(method); - malloc.free(allocatedNativeArguments); - malloc.free(dispatchResult); - malloc.free(returnValue); + return completer.future; } } @@ -105,7 +154,7 @@ abstract class BindingBridge { static Pointer> get nativeInvokeBindingMethod => _invokeBindingMethodFromNative; - static void createBindingObject(int contextId, Pointer pointer, CreateBindingObjectType type, Pointer args, int argc) { + static void createBindingObject(double contextId, Pointer pointer, CreateBindingObjectType type, Pointer args, int argc) { WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; List arguments = List.generate(argc, (index) { return fromNativeValue(controller.view, args.elementAt(index)); diff --git a/webf/lib/src/bridge/bridge.dart b/webf/lib/src/bridge/bridge.dart index ec809b8cfa..eb44693260 100644 --- a/webf/lib/src/bridge/bridge.dart +++ b/webf/lib/src/bridge/bridge.dart @@ -3,12 +3,14 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +import 'dart:async'; import 'dart:ffi'; import 'package:webf/launcher.dart'; import 'binding.dart'; import 'from_native.dart'; import 'to_native.dart'; +import 'multiple_thread.dart'; class DartContext { DartContext() : pointer = initDartIsolateContext(makeDartMethodsData()) { @@ -18,15 +20,21 @@ class DartContext { final Pointer pointer; } -DartContext dartContext = DartContext(); +DartContext? dartContext; + +bool isJSRunningInDedicatedThread(double contextId) { + return contextId >= 0; +} /// Init bridge -int initBridge(WebFViewController view) { +FutureOr initBridge(WebFViewController view, WebFThread runningThread) async { + dartContext ??= DartContext(); + // Setup binding bridge. BindingBridge.setup(); - int pageId = newPageId(); - allocateNewPage(pageId); + double newContextId = runningThread.identity(); + await allocateNewPage(runningThread is FlutterUIThread, newContextId); - return pageId; + return newContextId; } diff --git a/webf/lib/src/bridge/from_native.dart b/webf/lib/src/bridge/from_native.dart index 0453c4ba79..771eb55bd4 100644 --- a/webf/lib/src/bridge/from_native.dart +++ b/webf/lib/src/bridge/from_native.dart @@ -9,7 +9,9 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:webf/bridge.dart'; +import 'package:webf/foundation.dart'; import 'package:webf/launcher.dart'; +import 'package:webf/src/widget/widget_element.dart'; String uint16ToString(Pointer pointer, int length) { return String.fromCharCodes(pointer.asTypedList(length)); @@ -76,25 +78,70 @@ void freeNativeString(Pointer pointer) { // Register InvokeModule typedef NativeAsyncModuleCallback = Pointer Function( - Pointer callbackContext, Int32 contextId, Pointer errmsg, Pointer ptr); + Pointer callbackContext, + Double contextId, + Pointer errmsg, + Pointer ptr, + Handle context, + Pointer> handleResult); typedef DartAsyncModuleCallback = Pointer Function( - Pointer callbackContext, int contextId, Pointer errmsg, Pointer ptr); + Pointer callbackContext, + double contextId, + Pointer errmsg, + Pointer ptr, + Object context, + Pointer> handleResult); + +typedef NativeHandleInvokeModuleResult = Void Function(Handle context, Pointer result); typedef NativeInvokeModule = Pointer Function( Pointer callbackContext, - Int32 contextId, + Double contextId, Pointer module, Pointer method, Pointer params, Pointer>); +class _InvokeModuleResultContext { + Completer completer; + Pointer? errmsgPtr; + Stopwatch? stopwatch; + WebFViewController currentView; + Pointer? data; + String moduleName; + String method; + dynamic params; + + _InvokeModuleResultContext(this.completer, this.currentView, this.moduleName, this.method, this.params, + {this.errmsgPtr, this.data, this.stopwatch}); +} + +void _handleInvokeModuleResult(_InvokeModuleResultContext context, Pointer result) { + var returnValue = fromNativeValue(context.currentView, result); + + if (enableWebFCommandLog && context.stopwatch != null) { + print( + 'Invoke module callback from(name: ${context.moduleName} method: ${context.method}, params: ${context.params}) ' + 'return: $returnValue time: ${context.stopwatch!.elapsedMicroseconds}us'); + } + + malloc.free(result); + if (context.errmsgPtr != null) { + malloc.free(context.errmsgPtr!); + } else if (context.data != null) { + malloc.free(context.data!); + } + + context.completer.complete(returnValue); +} + dynamic invokeModule(Pointer callbackContext, WebFController controller, String moduleName, String method, params, DartAsyncModuleCallback callback) { WebFViewController currentView = controller.view; dynamic result; Stopwatch? stopwatch; - if (isEnabledLog) { + if (enableWebFCommandLog) { stopwatch = Stopwatch()..start(); } @@ -105,41 +152,39 @@ dynamic invokeModule(Pointer callbackContext, WebFController controller, S // We should make callback always async. Future.microtask(() { if (controller.view != currentView || currentView.disposed) return; - Pointer callbackResult = nullptr; + + Pointer> handleResult = + Pointer.fromFunction(_handleInvokeModuleResult); if (error != null) { Pointer errmsgPtr = error.toNativeUtf8(); - callbackResult = callback(callbackContext, currentView.contextId, errmsgPtr, nullptr); - malloc.free(errmsgPtr); + _InvokeModuleResultContext context = _InvokeModuleResultContext( + completer, currentView, moduleName, method, params, + errmsgPtr: errmsgPtr, stopwatch: stopwatch); + callback(callbackContext, currentView.contextId, errmsgPtr, nullptr, context, handleResult); } else { Pointer dataPtr = malloc.allocate(sizeOf()); toNativeValue(dataPtr, data); - callbackResult = callback(callbackContext, currentView.contextId, nullptr, dataPtr); - malloc.free(dataPtr); - } - - var returnValue = fromNativeValue(currentView, callbackResult); - if (isEnabledLog) { - print('Invoke module callback from(name: $moduleName method: $method, params: $params) return: $returnValue time: ${stopwatch!.elapsedMicroseconds}us'); + _InvokeModuleResultContext context = _InvokeModuleResultContext( + completer, currentView, moduleName, method, params, + data: dataPtr, stopwatch: stopwatch); + callback(callbackContext, currentView.contextId, nullptr, dataPtr, context, handleResult); } - - malloc.free(callbackResult); - completer.complete(returnValue); }); return completer.future; } - result = controller.module.moduleManager.invokeModule( - moduleName, method, params, invokeModuleCallback); + result = controller.module.moduleManager.invokeModule(moduleName, method, params, invokeModuleCallback); } catch (e, stack) { - if (isEnabledLog) { + if (enableWebFCommandLog) { print('Invoke module failed: $e\n$stack'); } String error = '$e\n$stack'; - callback(callbackContext, currentView.contextId, error.toNativeUtf8(), nullptr); + callback(callbackContext, currentView.contextId, error.toNativeUtf8(), nullptr, {}, nullptr); } - if (isEnabledLog) { - print('Invoke module name: $moduleName method: $method, params: $params return: $result time: ${stopwatch!.elapsedMicroseconds}us'); + if (enableWebFCommandLog) { + print('Invoke module name: $moduleName method: $method, params: $params ' + 'return: $result time: ${stopwatch!.elapsedMicroseconds}us'); } return result; @@ -147,7 +192,7 @@ dynamic invokeModule(Pointer callbackContext, WebFController controller, S Pointer _invokeModule( Pointer callbackContext, - int contextId, + double contextId, Pointer module, Pointer method, Pointer params, @@ -157,17 +202,15 @@ Pointer _invokeModule( fromNativeValue(controller.view, params), callback.asFunction()); Pointer returnValue = malloc.allocate(sizeOf()); toNativeValue(returnValue, result); - freeNativeString(module); - freeNativeString(method); return returnValue; } final Pointer> _nativeInvokeModule = Pointer.fromFunction(_invokeModule); // Register reloadApp -typedef NativeReloadApp = Void Function(Int32 contextId); +typedef NativeReloadApp = Void Function(Double contextId); -void _reloadApp(int contextId) async { +void _reloadApp(double contextId) async { WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; try { @@ -179,16 +222,16 @@ void _reloadApp(int contextId) async { final Pointer> _nativeReloadApp = Pointer.fromFunction(_reloadApp); -typedef NativeAsyncCallback = Void Function(Pointer callbackContext, Int32 contextId, Pointer errmsg); -typedef DartAsyncCallback = void Function(Pointer callbackContext, int contextId, Pointer errmsg); +typedef NativeAsyncCallback = Void Function(Pointer callbackContext, Double contextId, Pointer errmsg); +typedef DartAsyncCallback = void Function(Pointer callbackContext, double contextId, Pointer errmsg); typedef NativeRAFAsyncCallback = Void Function( - Pointer callbackContext, Int32 contextId, Double data, Pointer errmsg); -typedef DartRAFAsyncCallback = void Function(Pointer, int contextId, double data, Pointer errmsg); + Pointer callbackContext, Double contextId, Double data, Pointer errmsg); +typedef DartRAFAsyncCallback = void Function(Pointer, double contextId, double data, Pointer errmsg); // Register requestBatchUpdate -typedef NativeRequestBatchUpdate = Void Function(Int32 contextId); +typedef NativeRequestBatchUpdate = Void Function(Double contextId); -void _requestBatchUpdate(int contextId) { +void _requestBatchUpdate(double contextId) { WebFController? controller = WebFController.getControllerOfJSContextId(contextId); return controller?.module.requestBatchUpdate(); } @@ -197,15 +240,15 @@ final Pointer> _nativeRequestBatchUpdat Pointer.fromFunction(_requestBatchUpdate); // Register setTimeout -typedef NativeSetTimeout = Int32 Function( - Pointer callbackContext, Int32 contextId, Pointer>, Int32); +typedef NativeSetTimeout = Void Function(Int32 newTimerId, Pointer callbackContext, Double contextId, + Pointer>, Int32); -int _setTimeout( - Pointer callbackContext, int contextId, Pointer> callback, int timeout) { +void _setTimeout(int newTimerId, Pointer callbackContext, double contextId, + Pointer> callback, int timeout) { WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; WebFViewController currentView = controller.view; - return controller.module.setTimeout(timeout, () { + controller.module.setTimeout(newTimerId, timeout, () { DartAsyncCallback func = callback.asFunction(); void _runCallback() { if (controller.view != currentView || currentView.disposed) return; @@ -215,7 +258,6 @@ int _setTimeout( } catch (e, stack) { Pointer nativeErrorMessage = ('Error: $e\n$stack').toNativeUtf8(); func(callbackContext, contextId, nativeErrorMessage); - malloc.free(nativeErrorMessage); } } @@ -228,19 +270,17 @@ int _setTimeout( }); } -const int SET_TIMEOUT_ERROR = -1; -final Pointer> _nativeSetTimeout = - Pointer.fromFunction(_setTimeout, SET_TIMEOUT_ERROR); +final Pointer> _nativeSetTimeout = Pointer.fromFunction(_setTimeout); // Register setInterval -typedef NativeSetInterval = Int32 Function( - Pointer callbackContext, Int32 contextId, Pointer>, Int32); +typedef NativeSetInterval = Void Function(Int32 newTimerId, Pointer callbackContext, Double contextId, + Pointer>, Int32); -int _setInterval( - Pointer callbackContext, int contextId, Pointer> callback, int timeout) { +void _setInterval(int newTimerId, Pointer callbackContext, double contextId, + Pointer> callback, int timeout) { WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; WebFViewController currentView = controller.view; - return controller.module.setInterval(timeout, () { + controller.module.setInterval(newTimerId, timeout, () { void _runCallbacks() { if (controller.view != currentView || currentView.disposed) return; @@ -250,7 +290,6 @@ int _setInterval( } catch (e, stack) { Pointer nativeErrorMessage = ('Dart Error: $e\n$stack').toNativeUtf8(); func(callbackContext, contextId, nativeErrorMessage); - malloc.free(nativeErrorMessage); } } @@ -263,14 +302,12 @@ int _setInterval( }); } -const int SET_INTERVAL_ERROR = -1; -final Pointer> _nativeSetInterval = - Pointer.fromFunction(_setInterval, SET_INTERVAL_ERROR); +final Pointer> _nativeSetInterval = Pointer.fromFunction(_setInterval); // Register clearTimeout -typedef NativeClearTimeout = Void Function(Int32 contextId, Int32); +typedef NativeClearTimeout = Void Function(Double contextId, Int32); -void _clearTimeout(int contextId, int timerId) { +void _clearTimeout(double contextId, int timerId) { WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; return controller.module.clearTimeout(timerId); } @@ -278,14 +315,14 @@ void _clearTimeout(int contextId, int timerId) { final Pointer> _nativeClearTimeout = Pointer.fromFunction(_clearTimeout); // Register requestAnimationFrame -typedef NativeRequestAnimationFrame = Int32 Function( - Pointer callbackContext, Int32 contextId, Pointer>); +typedef NativeRequestAnimationFrame = Void Function( + Int32 newFrameId, Pointer callbackContext, Double contextId, Pointer>); -int _requestAnimationFrame( - Pointer callbackContext, int contextId, Pointer> callback) { +void _requestAnimationFrame(int newFrameId, Pointer callbackContext, double contextId, + Pointer> callback) { WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; WebFViewController currentView = controller.view; - return controller.module.requestAnimationFrame((double highResTimeStamp) { + controller.module.requestAnimationFrame(newFrameId, (double highResTimeStamp) { void _runCallback() { if (controller.view != currentView || currentView.disposed) return; DartRAFAsyncCallback func = callback.asFunction(); @@ -294,7 +331,6 @@ int _requestAnimationFrame( } catch (e, stack) { Pointer nativeErrorMessage = ('Error: $e\n$stack').toNativeUtf8(); func(callbackContext, contextId, highResTimeStamp, nativeErrorMessage); - malloc.free(nativeErrorMessage); } } @@ -307,14 +343,13 @@ int _requestAnimationFrame( }); } -const int RAF_ERROR_CODE = -1; final Pointer> _nativeRequestAnimationFrame = - Pointer.fromFunction(_requestAnimationFrame, RAF_ERROR_CODE); + Pointer.fromFunction(_requestAnimationFrame); // Register cancelAnimationFrame -typedef NativeCancelAnimationFrame = Void Function(Int32 contextId, Int32 id); +typedef NativeCancelAnimationFrame = Void Function(Double contextId, Int32 id); -void _cancelAnimationFrame(int contextId, int timerId) { +void _cancelAnimationFrame(double contextId, int timerId) { WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; controller.module.cancelAnimationFrame(timerId); } @@ -323,13 +358,13 @@ final Pointer> _nativeCancelAnimation Pointer.fromFunction(_cancelAnimationFrame); typedef NativeAsyncBlobCallback = Void Function( - Pointer callbackContext, Int32 contextId, Pointer, Pointer, Int32); + Pointer callbackContext, Double contextId, Pointer, Pointer, Int32); typedef DartAsyncBlobCallback = void Function( - Pointer callbackContext, int contextId, Pointer, Pointer, int); -typedef NativeToBlob = Void Function( - Pointer callbackContext, Int32 contextId, Pointer>, Pointer, Double); + Pointer callbackContext, double contextId, Pointer, Pointer, int); +typedef NativeToBlob = Void Function(Pointer callbackContext, Double contextId, + Pointer>, Pointer, Double); -void _toBlob(Pointer callbackContext, int contextId, Pointer> callback, +void _toBlob(Pointer callbackContext, double contextId, Pointer> callback, Pointer elementPtr, double devicePixelRatio) { DartAsyncBlobCallback func = callback.asFunction(); WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; @@ -338,60 +373,72 @@ void _toBlob(Pointer callbackContext, int contextId, Pointer nativeErrorMessage = ('$error\n$stack').toNativeUtf8(); func(callbackContext, contextId, nativeErrorMessage, nullptr, 0); - malloc.free(nativeErrorMessage); }); } final Pointer> _nativeToBlob = Pointer.fromFunction(_toBlob); -typedef NativeFlushUICommand = Void Function(Int32 contextId); -typedef DartFlushUICommand = void Function(int contextId); +typedef NativeFlushUICommand = Void Function(Double contextId, Pointer selfPointer, Uint32 reason); +typedef DartFlushUICommand = void Function(double contextId, Pointer selfPointer, int reason); -void _flushUICommand(int contextId) { - flushUICommandWithContextId(contextId); +void _flushUICommand(double contextId, Pointer selfPointer, int reason) { + flushUICommandWithContextId(contextId, selfPointer, reason); } final Pointer> _nativeFlushUICommand = Pointer.fromFunction(_flushUICommand); -typedef NativePerformanceGetEntries = Pointer Function(Int32 contextId); -typedef DartPerformanceGetEntries = Pointer Function(int contextId); - -typedef NativeCreateBindingObject = Void Function(Int32 contextId, Pointer nativeBindingObject, Int32 type, Pointer args, Int32 argc); -typedef DartCreateBindingObject = void Function(int contextId, Pointer nativeBindingObject, int type, Pointer args, int argc); +typedef NativeCreateBindingObject = Void Function(Double contextId, Pointer nativeBindingObject, + Int32 type, Pointer args, Int32 argc); +typedef DartCreateBindingObject = void Function( + double contextId, Pointer nativeBindingObject, int type, Pointer args, int argc); -void _createBindingObject(int contextId, Pointer nativeBindingObject, int type, Pointer args, int argc) { +void _createBindingObject( + double contextId, Pointer nativeBindingObject, int type, Pointer args, int argc) { BindingBridge.createBindingObject(contextId, nativeBindingObject, CreateBindingObjectType.values[type], args, argc); } -final Pointer> _nativeCreateBindingObject = Pointer.fromFunction(_createBindingObject); +final Pointer> _nativeCreateBindingObject = + Pointer.fromFunction(_createBindingObject); -Pointer _performanceGetEntries(int contextId) { - return nullptr; +typedef NativeGetWidgetElementShape = Int8 Function(Double contextId, Pointer nativeBindingObject, Pointer result); + +int _getWidgetElementShape(double contextId, Pointer nativeBindingObject, Pointer result) { + try { + WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; + DynamicBindingObject object = controller.view.getBindingObject(nativeBindingObject)!; + + if (object is WidgetElement) { + object.nativeGetPropertiesAndMethods(result); + return 1; + } + } catch (e, stack) { + print('$e\n$stack'); + } + return 0; } -final Pointer> _nativeGetEntries = - Pointer.fromFunction(_performanceGetEntries); +final Pointer> _nativeGetWidgetElementShape = Pointer.fromFunction(_getWidgetElementShape, 0); -typedef NativeJSError = Void Function(Int32 contextId, Pointer); +typedef NativeJSError = Void Function(Double contextId, Pointer); -void _onJSError(int contextId, Pointer charStr) { +void _onJSError(double contextId, Pointer charStr) { WebFController? controller = WebFController.getControllerOfJSContextId(contextId); JSErrorHandler? handler = controller?.onJSError; if (handler != null) { String msg = charStr.toDartString(); handler(msg); } + malloc.free(charStr); } final Pointer> _nativeOnJsError = Pointer.fromFunction(_onJSError); -typedef NativeJSLog = Void Function(Int32 contextId, Int32 level, Pointer); +typedef NativeJSLog = Void Function(Double contextId, Int32 level, Pointer); -void _onJSLog(int contextId, int level, Pointer charStr) { +void _onJSLog(double contextId, int level, Pointer charStr) { String msg = charStr.toDartString(); WebFController? controller = WebFController.getControllerOfJSContextId(contextId); if (controller != null) { @@ -400,6 +447,7 @@ void _onJSLog(int contextId, int level, Pointer charStr) { jsLogHandler(level, msg); } } + malloc.free(charStr); } final Pointer> _nativeOnJsLog = Pointer.fromFunction(_onJSLog); @@ -416,7 +464,7 @@ final List _dartNativeMethods = [ _nativeToBlob.address, _nativeFlushUICommand.address, _nativeCreateBindingObject.address, - _nativeGetEntries.address, + _nativeGetWidgetElementShape.address, _nativeOnJsError.address, _nativeOnJsLog.address, ]; diff --git a/webf/lib/src/bridge/multiple_thread.dart b/webf/lib/src/bridge/multiple_thread.dart new file mode 100644 index 0000000000..698df6e2a3 --- /dev/null +++ b/webf/lib/src/bridge/multiple_thread.dart @@ -0,0 +1,38 @@ +import 'to_native.dart'; + +abstract class WebFThread { + double identity(); +} + +/// Executes your JavaScript code within the Flutter UI thread. +class FlutterUIThread extends WebFThread { + FlutterUIThread(); + + @override + double identity() { + return (-newPageId()).toDouble(); + } +} + +/// Executes your JavaScript code in a dedicated thread. +class DedicatedThread extends WebFThread { + final double? _identity; + DedicatedThread([this._identity]); + + @override + double identity() { + return _identity ?? (newPageId()).toDouble(); + } +} + +/// Executes multiple JavaScript contexts in a single thread. +class DedicatedThreadGroup { + int _slaveCount = 0; + final int _identity = newPageId(); + DedicatedThreadGroup(); + + DedicatedThread slave() { + String input = '$_identity.${_slaveCount++}'; + return DedicatedThread(double.parse(input)); + } +} diff --git a/webf/lib/src/bridge/native_types.dart b/webf/lib/src/bridge/native_types.dart index 47872e62d8..69d98f41a0 100644 --- a/webf/lib/src/bridge/native_types.dart +++ b/webf/lib/src/bridge/native_types.dart @@ -109,13 +109,12 @@ class NativeTouch extends Struct { external double azimuthAngle; } -typedef InvokeBindingsMethodsFromNative = Void Function(Int32 contextId, Pointer binding_object, +typedef InvokeBindingsMethodsFromNative = Void Function(Double contextId, Pointer binding_object, Pointer return_value, Pointer method, Int32 argc, Pointer argv); +typedef NativeInvokeResultCallback = Void Function(Handle object, Pointer result); -typedef InvokeBindingMethodsFromDart = Void Function(Pointer binding_object, - Pointer return_value, Pointer method, Int32 argc, Pointer argv, Handle bindingDartObject); -typedef DartInvokeBindingMethodsFromDart = void Function(Pointer binding_object, - Pointer return_value, Pointer method, int argc, Pointer argv, Object bindingDartObject); +typedef InvokeBindingMethodsFromDart = Void Function(Pointer binding_object, Pointer method, Int32 argc, Pointer argv, Handle bindingDartObject, Pointer> result_callback); +typedef DartInvokeBindingMethodsFromDart = void Function(Pointer binding_object, Pointer method, int argc, Pointer argv, Object bindingDartObject, Pointer> result_callback); class NativeBindingObject extends Struct { @Bool() @@ -124,6 +123,7 @@ class NativeBindingObject extends Struct { external Pointer> invokeBindingMethodFromDart; // Shared method called by JS side. external Pointer> invokeBindingMethodFromNative; + external Pointer extra; } Pointer allocateNewBindingObject() { diff --git a/webf/lib/src/bridge/to_native.dart b/webf/lib/src/bridge/to_native.dart index 726d350369..f4f6ece55c 100644 --- a/webf/lib/src/bridge/to_native.dart +++ b/webf/lib/src/bridge/to_native.dart @@ -3,13 +3,13 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +import 'dart:async'; import 'dart:collection'; import 'dart:ffi'; -import 'dart:io'; +import 'dart:isolate'; import 'dart:typed_data'; import 'package:ffi/ffi.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/scheduler.dart'; import 'package:webf/webf.dart'; @@ -55,9 +55,9 @@ final DartGetWebFInfo _getWebFInfo = final WebFInfo _cachedInfo = WebFInfo(_getWebFInfo()); -final HashMap> _allocatedPages = HashMap(); +final HashMap> _allocatedPages = HashMap(); -Pointer? getAllocatedPage(int contextId) { +Pointer? getAllocatedPage(double contextId) { return _allocatedPages[contextId]; } @@ -65,37 +65,129 @@ WebFInfo getWebFInfo() { return _cachedInfo; } +// Register Native Callback Port +final interactiveCppRequests = RawReceivePort((message) { + requestExecuteCallback(message); +}); + +final int nativePort = interactiveCppRequests.sendPort.nativePort; + +class NativeWork extends Opaque {} + +final _executeNativeCallback = WebFDynamicLibrary.ref + .lookupFunction), void Function(Pointer)>('executeNativeCallback'); + +Completer? _working_completer; + +FutureOr waitingSyncTaskComplete(double contextId) async { + if (_working_completer != null) { + return _working_completer!.future; + } + + bool isBlocked = isJSThreadBlocked(contextId); + if (isBlocked) { + Completer completer = Completer(); + SchedulerBinding.instance.addPostFrameCallback((_) async { + await waitingSyncTaskComplete(contextId); + completer.complete(); + }); + SchedulerBinding.instance.scheduleFrame(); + return completer.future; + } +} + +void requestExecuteCallback(message) { + try { + final List data = message; + final bool isSync = data[0] == 1; + if (isSync) { + _working_completer = Completer(); + } + + final int workAddress = data[1]; + final work = Pointer.fromAddress(workAddress); + _executeNativeCallback(work); + _working_completer?.complete(); + _working_completer = null; + } catch (e, stack) { + print('requestExecuteCallback error: $e\n$stack'); + } +} + // Register invokeEventListener -typedef NativeInvokeEventListener = Pointer Function( - Pointer, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); -typedef DartInvokeEventListener = Pointer Function( - Pointer, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); +typedef NativeInvokeEventListener = Void Function( + Pointer, + Pointer, + Pointer eventType, + Pointer nativeEvent, + Pointer, + Handle object, + Pointer> returnCallback); +typedef DartInvokeEventListener = void Function( + Pointer, + Pointer, + Pointer eventType, + Pointer nativeEvent, + Pointer, + Object object, + Pointer> returnCallback); +typedef NativeInvokeModuleCallback = Void Function(Handle object, Pointer result); final DartInvokeEventListener _invokeModuleEvent = WebFDynamicLibrary.ref.lookup>('invokeModuleEvent').asFunction(); -dynamic invokeModuleEvent(int contextId, String moduleName, Event? event, extra) { +void _invokeModuleCallback(_InvokeModuleCallbackContext context, Pointer dispatchResult) { + dynamic result = fromNativeValue(context.controller.view, dispatchResult); + malloc.free(dispatchResult); + malloc.free(context.extraData); + context.completer.complete(result); +} + +class _InvokeModuleCallbackContext { + Completer completer; + WebFController controller; + Pointer extraData; + + _InvokeModuleCallbackContext(this.completer, this.controller, this.extraData); +} + +dynamic invokeModuleEvent(double contextId, String moduleName, Event? event, extra) { if (WebFController.getControllerOfJSContextId(contextId) == null) { return null; } + Completer completer = Completer(); WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; + + if (controller.view.disposed) return null; + Pointer nativeModuleName = stringToNativeString(moduleName); Pointer rawEvent = event == null ? nullptr : event.toRaw().cast(); Pointer extraData = malloc.allocate(sizeOf()); toNativeValue(extraData, extra); assert(_allocatedPages.containsKey(contextId)); - Pointer dispatchResult = _invokeModuleEvent( - _allocatedPages[contextId]!, nativeModuleName, event == null ? nullptr : event.type.toNativeUtf8(), rawEvent, extraData); - dynamic result = fromNativeValue(controller.view, dispatchResult); - malloc.free(dispatchResult); - malloc.free(extraData); - return result; + + Pointer> callback = + Pointer.fromFunction(_invokeModuleCallback); + + _InvokeModuleCallbackContext callbackContext = _InvokeModuleCallbackContext(completer, controller, extraData); + + scheduleMicrotask(() { + if (controller.view.disposed) { + callbackContext.completer.complete(null); + return; + } + + _invokeModuleEvent(_allocatedPages[contextId]!, nativeModuleName, + event == null ? nullptr : event.type.toNativeUtf8(), rawEvent, extraData, callbackContext, callback); + }); + + return completer.future; } -typedef DartDispatchEvent = int Function(int contextId, Pointer nativeBindingObject, +typedef DartDispatchEvent = int Function(double contextId, Pointer nativeBindingObject, Pointer eventType, Pointer nativeEvent, int isCustomEvent); -dynamic emitModuleEvent(int contextId, String moduleName, Event? event, extra) { +dynamic emitModuleEvent(double contextId, String moduleName, Event? event, extra) { return invokeModuleEvent(contextId, moduleName, event, extra); } @@ -111,10 +203,28 @@ Pointer createScreen(double width, double height) { } // Register evaluateScripts -typedef NativeEvaluateScripts = Int8 Function( - Pointer, Pointer code, Uint64 code_len, Pointer> parsedBytecodes, Pointer bytecodeLen, Pointer url, Int32 startLine); -typedef DartEvaluateScripts = int Function( - Pointer, Pointer code, int code_len, Pointer> parsedBytecodes, Pointer bytecodeLen, Pointer url, int startLine); +typedef NativeEvaluateScripts = Void Function( + Pointer, + Pointer code, + Uint64 code_len, + Pointer> parsedBytecodes, + Pointer bytecodeLen, + Pointer url, + Int32 startLine, + Handle object, + Pointer> resultCallback); +typedef DartEvaluateScripts = void Function( + Pointer, + Pointer code, + int code_len, + Pointer> parsedBytecodes, + Pointer bytecodeLen, + Pointer url, + int startLine, + Object object, + Pointer> resultCallback); + +typedef NativeEvaluateJavaScriptCallback = Void Function(Handle object, Int8 result); // Register parseHTML typedef NativeParseHTML = Void Function(Pointer, Pointer code, Int32 length); @@ -129,7 +239,8 @@ final DartParseHTML _parseHTML = typedef NativeParseSVGResult = Pointer Function(Pointer code, Int32 length); typedef DartParseSVGResult = Pointer Function(Pointer code, int length); -final _parseSVGResult = WebFDynamicLibrary.ref.lookupFunction('parseSVGResult'); +final _parseSVGResult = + WebFDynamicLibrary.ref.lookupFunction('parseSVGResult'); typedef NativeFreeSVGResult = Void Function(Pointer ptr); typedef DartFreeSVGResult = void Function(Pointer ptr); @@ -140,10 +251,38 @@ int _anonymousScriptEvaluationId = 0; class ScriptByteCode { ScriptByteCode(); + late Uint8List bytes; } -Future evaluateScripts(int contextId, Uint8List codeBytes, {String? url, int line = 0}) async { +class _EvaluateScriptsContext { + Completer completer; + Pointer codePtr; + Pointer url; + Pointer>? bytecodes; + Pointer? bytecodeLen; + Uint8List originalCodeBytes; + + _EvaluateScriptsContext(this.completer, this.originalCodeBytes, this.codePtr, this.url); +} + +void handleEvaluateScriptsResult(_EvaluateScriptsContext context, int result) { + if (context.bytecodes != null) { + Uint8List bytes = context.bytecodes!.value.asTypedList(context.bytecodeLen!.value); + // Save to disk cache + QuickJSByteCodeCache.putObject(context.originalCodeBytes, bytes).then((_) { + malloc.free(context.codePtr); + malloc.free(context.url); + context.completer.complete(result == 1); + }); + } else { + malloc.free(context.codePtr); + malloc.free(context.url); + context.completer.complete(result == 1); + } +} + +Future evaluateScripts(double contextId, Uint8List codeBytes, {String? url, int line = 0}) async { if (WebFController.getControllerOfJSContextId(contextId) == null) { return false; } @@ -154,8 +293,10 @@ Future evaluateScripts(int contextId, Uint8List codeBytes, {String? url, i } QuickJSByteCodeCacheObject cacheObject = await QuickJSByteCodeCache.getCacheObject(codeBytes); - if (QuickJSByteCodeCacheObject.cacheMode == ByteCodeCacheMode.DEFAULT && cacheObject.valid && cacheObject.bytes != null) { - bool result = evaluateQuickjsByteCode(contextId, cacheObject.bytes!); + if (QuickJSByteCodeCacheObject.cacheMode == ByteCodeCacheMode.DEFAULT && + cacheObject.valid && + cacheObject.bytes != null) { + bool result = await evaluateQuickjsByteCode(contextId, cacheObject.bytes!); // If the bytecode evaluate failed, remove the cached file and fallback to raw javascript mode. if (!result) { await cacheObject.remove(); @@ -165,50 +306,80 @@ Future evaluateScripts(int contextId, Uint8List codeBytes, {String? url, i } else { Pointer _url = url.toNativeUtf8(); Pointer codePtr = uint8ListToPointer(codeBytes); + Completer completer = Completer(); + + _EvaluateScriptsContext context = _EvaluateScriptsContext(completer, codeBytes, codePtr, _url); + Pointer> resultCallback = + Pointer.fromFunction(handleEvaluateScriptsResult); + try { assert(_allocatedPages.containsKey(contextId)); - int result; if (QuickJSByteCodeCache.isCodeNeedCache(codeBytes)) { // Export the bytecode from scripts Pointer> bytecodes = malloc.allocate(sizeOf>()); Pointer bytecodeLen = malloc.allocate(sizeOf()); - result = _evaluateScripts(_allocatedPages[contextId]!, codePtr, codeBytes.length, bytecodes, bytecodeLen, _url, line); - Uint8List bytes = bytecodes.value.asTypedList(bytecodeLen.value); - // Save to disk cache - QuickJSByteCodeCache.putObject(codeBytes, bytes); + + context.bytecodes = bytecodes; + context.bytecodeLen = bytecodeLen; + + _evaluateScripts(_allocatedPages[contextId]!, codePtr, codeBytes.length, bytecodes, bytecodeLen, _url, line, + context, resultCallback); } else { - result = _evaluateScripts(_allocatedPages[contextId]!, codePtr, codeBytes.length, nullptr, nullptr, _url, line); + _evaluateScripts(_allocatedPages[contextId]!, codePtr, codeBytes.length, nullptr, nullptr, _url, line, context, + resultCallback); } - return result == 1; + return completer.future; } catch (e, stack) { print('$e\n$stack'); } - malloc.free(codePtr); - malloc.free(_url); + + return completer.future; } - return false; } -typedef NativeEvaluateQuickjsByteCode = Int8 Function(Pointer, Pointer bytes, Int32 byteLen); -typedef DartEvaluateQuickjsByteCode = int Function(Pointer, Pointer bytes, int byteLen); +typedef NativeEvaluateQuickjsByteCode = Void Function(Pointer, Pointer bytes, Int32 byteLen, Handle object, + Pointer> callback); +typedef DartEvaluateQuickjsByteCode = void Function(Pointer, Pointer bytes, int byteLen, Object object, + Pointer> callback); + +typedef NativeEvaluateQuickjsByteCodeCallback = Void Function(Handle object, Int8 result); final DartEvaluateQuickjsByteCode _evaluateQuickjsByteCode = WebFDynamicLibrary.ref .lookup>('evaluateQuickjsByteCode') .asFunction(); -bool evaluateQuickjsByteCode(int contextId, Uint8List bytes) { +class _EvaluateQuickjsByteCodeContext { + Completer completer; + Pointer bytes; + + _EvaluateQuickjsByteCodeContext(this.completer, this.bytes); +} + +void handleEvaluateQuickjsByteCodeResult(_EvaluateQuickjsByteCodeContext context, int result) { + malloc.free(context.bytes); + context.completer.complete(result == 1); +} + +Future evaluateQuickjsByteCode(double contextId, Uint8List bytes) async { if (WebFController.getControllerOfJSContextId(contextId) == null) { return false; } + Completer completer = Completer(); Pointer byteData = malloc.allocate(sizeOf() * bytes.length); byteData.asTypedList(bytes.length).setAll(0, bytes); assert(_allocatedPages.containsKey(contextId)); - int result = _evaluateQuickjsByteCode(_allocatedPages[contextId]!, byteData, bytes.length); - malloc.free(byteData); - return result == 1; + + _EvaluateQuickjsByteCodeContext context = _EvaluateQuickjsByteCodeContext(completer, byteData); + + Pointer> nativeCallback = + Pointer.fromFunction(handleEvaluateQuickjsByteCodeResult); + + _evaluateQuickjsByteCode(_allocatedPages[contextId]!, byteData, bytes.length, context, nativeCallback); + + return completer.future; } -void parseHTML(int contextId, Uint8List codeBytes) { +void parseHTML(double contextId, Uint8List codeBytes) { if (WebFController.getControllerOfJSContextId(contextId) == null) { return; } @@ -219,12 +390,12 @@ void parseHTML(int contextId, Uint8List codeBytes) { } catch (e, stack) { print('$e\n$stack'); } - malloc.free(codePtr); } class GumboOutput { final Pointer ptr; final Pointer source; + GumboOutput(this.ptr, this.source); } @@ -240,57 +411,121 @@ void freeSVGResult(GumboOutput gumboOutput) { } // Register initJsEngine -typedef NativeInitDartIsolateContext = Pointer Function(Pointer dartMethods, Int32 methodsLength); -typedef DartInitDartIsolateContext = Pointer Function(Pointer dartMethods, int methodsLength); +typedef NativeInitDartIsolateContext = Pointer Function( + Int64 sendPort, Pointer dartMethods, Int32 methodsLength); +typedef DartInitDartIsolateContext = Pointer Function( + int sendPort, Pointer dartMethods, int methodsLength); -final DartInitDartIsolateContext _initDartIsolateContext = - WebFDynamicLibrary.ref.lookup>('initDartIsolateContext').asFunction(); +final DartInitDartIsolateContext _initDartIsolateContext = WebFDynamicLibrary.ref + .lookup>('initDartIsolateContextSync') + .asFunction(); Pointer initDartIsolateContext(List dartMethods) { Pointer bytes = malloc.allocate(sizeOf() * dartMethods.length); Uint64List nativeMethodList = bytes.asTypedList(dartMethods.length); nativeMethodList.setAll(0, dartMethods); - return _initDartIsolateContext(bytes, dartMethods.length); + return _initDartIsolateContext(nativePort, bytes, dartMethods.length); } -typedef NativeDisposePage = Void Function(Pointer, Pointer page); -typedef DartDisposePage = void Function(Pointer, Pointer page); +typedef HandleDisposePageResult = Void Function(Handle context); +typedef NativeDisposePage = Void Function(Double contextId, Pointer, Pointer page, Handle context, + Pointer> resultCallback); +typedef DartDisposePage = void Function(double, Pointer, Pointer page, Object context, + Pointer> resultCallback); final DartDisposePage _disposePage = WebFDynamicLibrary.ref.lookup>('disposePage').asFunction(); -void disposePage(int contextId) { +typedef NativeDisposePageSync = Void Function(Double contextId, Pointer, Pointer page); +typedef DartDisposePageSync = void Function(double, Pointer, Pointer page); + +final DartDisposePageSync _disposePageSync = + WebFDynamicLibrary.ref.lookup>('disposePageSync').asFunction(); + +void _handleDisposePageResult(_DisposePageContext context) { + context.completer.complete(); +} + +class _DisposePageContext { + Completer completer; + + _DisposePageContext(this.completer); +} + +FutureOr disposePage(bool isSync, double contextId) async { Pointer page = _allocatedPages[contextId]!; - _disposePage(dartContext.pointer, page); - _allocatedPages.remove(contextId); + + if (isSync) { + _disposePageSync(contextId, dartContext!.pointer, page); + _allocatedPages.remove(contextId); + } else { + Completer completer = Completer(); + _DisposePageContext context = _DisposePageContext(completer); + Pointer> f = Pointer.fromFunction(_handleDisposePageResult); + _disposePage(contextId, dartContext!.pointer, page, context, f); + return completer.future; + } } typedef NativeNewPageId = Int64 Function(); typedef DartNewPageId = int Function(); -final DartNewPageId _newPageId = WebFDynamicLibrary.ref.lookup>('newPageId').asFunction(); +final DartNewPageId _newPageId = + WebFDynamicLibrary.ref.lookup>('newPageIdSync').asFunction(); int newPageId() { return _newPageId(); } -typedef NativeAllocateNewPage = Pointer Function(Pointer, Int32); -typedef DartAllocateNewPage = Pointer Function(Pointer, int); +typedef NativeAllocateNewPageSync = Pointer Function(Double, Pointer); +typedef DartAllocateNewPageSync = Pointer Function(double, Pointer); +typedef HandleAllocateNewPageResult = Void Function(Handle object, Pointer page); +typedef NativeAllocateNewPage = Void Function( + Double, Pointer, Handle object, Pointer> handle_result); +typedef DartAllocateNewPage = void Function( + double, Pointer, Object object, Pointer> handle_result); + +final DartAllocateNewPageSync _allocateNewPageSync = + WebFDynamicLibrary.ref.lookup>('allocateNewPageSync').asFunction(); final DartAllocateNewPage _allocateNewPage = WebFDynamicLibrary.ref.lookup>('allocateNewPage').asFunction(); -void allocateNewPage(int targetContextId) { - Pointer page = _allocateNewPage(dartContext.pointer, targetContextId); - assert(!_allocatedPages.containsKey(targetContextId)); - _allocatedPages[targetContextId] = page; +void _handleAllocateNewPageResult(_AllocateNewPageContext context, Pointer page) { + assert(!_allocatedPages.containsKey(context.contextId)); + _allocatedPages[context.contextId] = page; + context.completer.complete(); +} + +class _AllocateNewPageContext { + Completer completer; + double contextId; + + _AllocateNewPageContext(this.completer, this.contextId); +} + +Future allocateNewPage(bool sync, double newContextId) async { + await waitingSyncTaskComplete(newContextId); + + if (!sync) { + Completer completer = Completer(); + _AllocateNewPageContext context = _AllocateNewPageContext(completer, newContextId); + Pointer> f = Pointer.fromFunction(_handleAllocateNewPageResult); + _allocateNewPage(newContextId, dartContext!.pointer, context, f); + return completer.future; + } else { + Pointer page = _allocateNewPageSync(newContextId, dartContext!.pointer); + assert(!_allocatedPages.containsKey(newContextId)); + _allocatedPages[newContextId] = page; + } } typedef NativeInitDartDynamicLinking = Void Function(Pointer data); typedef DartInitDartDynamicLinking = void Function(Pointer data); -final DartInitDartDynamicLinking _initDartDynamicLinking = - WebFDynamicLibrary.ref.lookup>('init_dart_dynamic_linking').asFunction(); +final DartInitDartDynamicLinking _initDartDynamicLinking = WebFDynamicLibrary.ref + .lookup>('init_dart_dynamic_linking') + .asFunction(); void initDartDynamicLinking() { _initDartDynamicLinking(NativeApi.initializeApiDLData); @@ -299,8 +534,9 @@ void initDartDynamicLinking() { typedef NativeRegisterDartContextFinalizer = Void Function(Handle object, Pointer dart_context); typedef DartRegisterDartContextFinalizer = void Function(Object object, Pointer dart_context); -final DartRegisterDartContextFinalizer _registerDartContextFinalizer = - WebFDynamicLibrary.ref.lookup>('register_dart_context_finalizer').asFunction(); +final DartRegisterDartContextFinalizer _registerDartContextFinalizer = WebFDynamicLibrary.ref + .lookup>('register_dart_context_finalizer') + .asFunction(); void registerDartContextFinalizer(DartContext dartContext) { _registerDartContextFinalizer(dartContext, dartContext.pointer); @@ -330,14 +566,15 @@ bool profileModeEnabled() { return _profileModeEnabled() == _CODE_ENABLED; } -typedef NativeDispatchUITask = Void Function(Int32 contextId, Pointer context, Pointer callback); -typedef DartDispatchUITask = void Function(int contextId, Pointer context, Pointer callback); +typedef NativeDispatchUITask = Void Function(Double contextId, Pointer context, Pointer callback); +typedef DartDispatchUITask = void Function(double contextId, Pointer context, Pointer callback); -void dispatchUITask(int contextId, Pointer context, Pointer callback) { +void dispatchUITask(double contextId, Pointer context, Pointer callback) { // _dispatchUITask(contextId, context, callback); } enum UICommandType { + startRecordingCommand, createElement, createTextNode, createComment, @@ -357,6 +594,7 @@ enum UICommandType { // perf optimize createSVGElement, createElementNS, + finishRecordingCommand, } class UICommandItem extends Struct { @@ -374,12 +612,41 @@ class UICommandItem extends Struct { external Pointer nativePtr; } +typedef NativeAcquireUiCommandLocks = Pointer Function(Pointer); +typedef DartAcquireUiCommandLocks = Pointer Function(Pointer); + +final DartAcquireUiCommandLocks _acquireUiCommandLocks = + WebFDynamicLibrary.ref.lookup>('acquireUiCommandLocks').asFunction(); + +void acquireUICommandLocks(double contextId) { + // Stop the mutations from JavaScript thread. + _acquireUiCommandLocks(_allocatedPages[contextId]!); +} + +typedef NativeReleaseUiCommandLocks = Pointer Function(Pointer); +typedef DartReleaseUiCommandLocks = Pointer Function(Pointer); + +final DartReleaseUiCommandLocks _releaseUiCommandLocks = + WebFDynamicLibrary.ref.lookup>('releaseUiCommandLocks').asFunction(); + +void releaseUICommandLocks(double contextId) { + // Stop the mutations from JavaScript thread. + _releaseUiCommandLocks(_allocatedPages[contextId]!); +} + typedef NativeGetUICommandItems = Pointer Function(Pointer); typedef DartGetUICommandItems = Pointer Function(Pointer); final DartGetUICommandItems _getUICommandItems = WebFDynamicLibrary.ref.lookup>('getUICommandItems').asFunction(); +typedef NativeGetUICommandKindFlags = Uint32 Function(Pointer); +typedef DartGetUICommandKindFlags = int Function(Pointer); + +final DartGetUICommandKindFlags _getUICommandKindFlags = + WebFDynamicLibrary.ref.lookup>('getUICommandKindFlag').asFunction(); + + typedef NativeGetUICommandItemSize = Int64 Function(Pointer); typedef DartGetUICommandItemSize = int Function(Pointer); @@ -392,221 +659,82 @@ typedef DartClearUICommandItems = void Function(Pointer); final DartClearUICommandItems _clearUICommandItems = WebFDynamicLibrary.ref.lookup>('clearUICommandItems').asFunction(); -class UICommand { - late final UICommandType type; - late final String args; - late final Pointer nativePtr; - late final Pointer nativePtr2; +typedef NativeIsJSThreadBlocked = Int8 Function(Pointer, Double); +typedef DartIsJSThreadBlocked = int Function(Pointer, double); - @override - String toString() { - return 'UICommand(type: $type, args: $args, nativePtr: $nativePtr, nativePtr2: $nativePtr2)'; - } -} - -// struct UICommandItem { -// int32_t type; // offset: 0 ~ 0.5 -// int32_t args_01_length; // offset: 0.5 ~ 1 -// const uint16_t *string_01;// offset: 1 -// void* nativePtr; // offset: 2 -// void* nativePtr2; // offset: 3 -// }; -const int nativeCommandSize = 4; -const int typeAndArgs01LenMemOffset = 0; -const int args01StringMemOffset = 1; -const int nativePtrMemOffset = 2; -const int native2PtrMemOffset = 3; - -final bool isEnabledLog = !kReleaseMode && Platform.environment['ENABLE_WEBF_JS_LOG'] == 'true'; - -// We found there are performance bottleneck of reading native memory with Dart FFI API. -// So we align all UI instructions to a whole block of memory, and then convert them into a dart array at one time, -// To ensure the fastest subsequent random access. -List readNativeUICommandToDart(Pointer nativeCommandItems, int commandLength, int contextId) { - List rawMemory = - nativeCommandItems.cast().asTypedList(commandLength * nativeCommandSize).toList(growable: false); - List results = List.generate(commandLength, (int _i) { - int i = _i * nativeCommandSize; - UICommand command = UICommand(); - - int typeArgs01Combine = rawMemory[i + typeAndArgs01LenMemOffset]; - - // int32_t int32_t - // +-------------+-----------------+ - // | type | args_01_length | - // +-------------+-----------------+ - int args01Length = (typeArgs01Combine >> 32).toSigned(32); - int type = (typeArgs01Combine ^ (args01Length << 32)).toSigned(32); - - command.type = UICommandType.values[type]; - - int args01StringMemory = rawMemory[i + args01StringMemOffset]; - if (args01StringMemory != 0) { - Pointer args_01 = Pointer.fromAddress(args01StringMemory); - command.args = uint16ToString(args_01, args01Length); - malloc.free(args_01); - } else { - command.args = ''; - } +final DartIsJSThreadBlocked _isJSThreadBlocked = + WebFDynamicLibrary.ref.lookup>('isJSThreadBlocked').asFunction(); - int nativePtrValue = rawMemory[i + nativePtrMemOffset]; - command.nativePtr = nativePtrValue != 0 ? Pointer.fromAddress(rawMemory[i + nativePtrMemOffset]) : nullptr; +bool isJSThreadBlocked(double contextId) { + return _isJSThreadBlocked(dartContext!.pointer, contextId) == 1; +} - int nativePtr2Value = rawMemory[i + native2PtrMemOffset]; - command.nativePtr2 = nativePtr2Value != 0 ? Pointer.fromAddress(nativePtr2Value) : nullptr; +void clearUICommand(double contextId) { + assert(_allocatedPages.containsKey(contextId)); - if (isEnabledLog) { - String printMsg = 'nativePtr: ${command.nativePtr} type: ${command.type} args: ${command.args} nativePtr2: ${command.nativePtr2}'; - print(printMsg); - } - return command; - }, growable: false); + // Stop the mutations from JavaScript thread. + acquireUICommandLocks(contextId); - // Clear native command. _clearUICommandItems(_allocatedPages[contextId]!); - return results; -} - -void clearUICommand(int contextId) { - assert(_allocatedPages.containsKey(contextId)); - _clearUICommandItems(_allocatedPages[contextId]!); + // Release the mutations from JavaScript thread. + releaseUICommandLocks(contextId); } -void flushUICommandWithContextId(int contextId) { +void flushUICommandWithContextId(double contextId, Pointer selfPointer, int reason) { WebFController? controller = WebFController.getControllerOfJSContextId(contextId); if (controller != null) { - flushUICommand(controller.view); + flushUICommand(controller.view, selfPointer, reason); } } -void flushUICommand(WebFViewController view) { - assert(_allocatedPages.containsKey(view.contextId)); - Pointer nativeCommandItems = _getUICommandItems(_allocatedPages[view.contextId]!); - int commandLength = _getUICommandItemSize(_allocatedPages[view.contextId]!); - - if (commandLength == 0 || nativeCommandItems == nullptr) { - return; +class _NativeCommandData { + static _NativeCommandData empty() { + return _NativeCommandData(0, 0, []); } - List commands = readNativeUICommandToDart(nativeCommandItems, commandLength, view.contextId); + int length; + int flag; + List rawMemory; - SchedulerBinding.instance.scheduleFrame(); + _NativeCommandData(this.flag, this.length, this.rawMemory); +} - Map pendingStylePropertiesTargets = {}; - Set pendingRecalculateTargets = {}; +_NativeCommandData readNativeUICommandMemory(double contextId) { + // Stop the mutations from JavaScript thread. + acquireUICommandLocks(contextId); - // For new ui commands, we needs to tell engine to update frames. - for (int i = 0; i < commandLength; i++) { - UICommand command = commands[i]; - UICommandType commandType = command.type; - Pointer nativePtr = command.nativePtr; + Pointer nativeCommandItemPointer = _getUICommandItems(_allocatedPages[contextId]!); + int flag = _getUICommandKindFlags(_allocatedPages[contextId]!); + int commandLength = _getUICommandItemSize(_allocatedPages[contextId]!); - try { - switch (commandType) { - case UICommandType.createElement: - view.createElement(nativePtr.cast(), command.args); - break; - case UICommandType.createDocument: - view.initDocument(view, nativePtr.cast()); - break; - case UICommandType.createWindow: - view.initWindow(view, nativePtr.cast()); - break; - case UICommandType.createTextNode: - view.createTextNode(nativePtr.cast(), command.args); - break; - case UICommandType.createComment: - view.createComment(nativePtr.cast()); - break; - case UICommandType.disposeBindingObject: - view.disposeBindingObject(view, nativePtr.cast()); - break; - case UICommandType.addEvent: - Pointer eventListenerOptions = command.nativePtr2.cast(); - view.addEvent(nativePtr.cast(), command.args, addEventListenerOptions: eventListenerOptions); - break; - case UICommandType.removeEvent: - bool isCapture = command.nativePtr2.address == 1; - view.removeEvent(nativePtr.cast(), command.args, isCapture: isCapture); - break; - case UICommandType.insertAdjacentNode: - view.insertAdjacentNode( - nativePtr.cast(), - command.args, - command.nativePtr2.cast() - ); - break; - case UICommandType.removeNode: - view.removeNode(nativePtr.cast()); - break; - case UICommandType.cloneNode: - view.cloneNode(nativePtr.cast(), command.nativePtr2.cast()); - break; - case UICommandType.setStyle: - String value; - if (command.nativePtr2 != nullptr) { - Pointer nativeValue = command.nativePtr2.cast(); - value = nativeStringToString(nativeValue); - freeNativeString(nativeValue); - } else { - value = ''; - } - view.setInlineStyle(nativePtr, command.args, value); - pendingStylePropertiesTargets[nativePtr.address] = true; - break; - case UICommandType.clearStyle: - view.clearInlineStyle(nativePtr); - pendingStylePropertiesTargets[nativePtr.address] = true; - break; - case UICommandType.setAttribute: - Pointer nativeKey = command.nativePtr2.cast(); - String key = nativeStringToString(nativeKey); - freeNativeString(nativeKey); - view.setAttribute(nativePtr.cast(), key, command.args); - pendingRecalculateTargets.add(nativePtr.address); - break; - case UICommandType.removeAttribute: - String key = command.args; - view.removeAttribute(nativePtr, key); - break; - case UICommandType.createDocumentFragment: - view.createDocumentFragment(nativePtr.cast()); - break; - case UICommandType.createSVGElement: - view.createElementNS(nativePtr.cast(), SVG_ELEMENT_URI, command.args); - break; - case UICommandType.createElementNS: - Pointer nativeNameSpaceUri = command.nativePtr2.cast(); - String namespaceUri = nativeStringToString(nativeNameSpaceUri); - freeNativeString(nativeNameSpaceUri); - - view.createElementNS(nativePtr.cast(), namespaceUri, command.args); - break; - default: - break; - } - } catch (e, stack) { - print('$e\n$stack'); - } + if (commandLength == 0 || nativeCommandItemPointer == nullptr) { + releaseUICommandLocks(contextId); + return _NativeCommandData.empty(); } - // For pending style properties, we needs to flush to render style. - for (int address in pendingStylePropertiesTargets.keys) { - try { - view.flushPendingStyleProperties(address); - } catch (e, stack) { - print('$e\n$stack'); - } - } - pendingStylePropertiesTargets.clear(); + List rawMemory = nativeCommandItemPointer + .cast() + .asTypedList((commandLength) * nativeCommandSize) + .toList(growable: false); + _clearUICommandItems(_allocatedPages[contextId]!); - for (var address in pendingRecalculateTargets) { - try { - view.recalculateStyle(address); - } catch(e, stack) { - print('$e\n$stack'); - } + // Release the mutations from JavaScript thread. + releaseUICommandLocks(contextId); + + return _NativeCommandData(flag, commandLength, rawMemory); +} + +void flushUICommand(WebFViewController view, Pointer selfPointer, int reason) { + assert(_allocatedPages.containsKey(view.contextId)); + if (view.disposed) return; + + _NativeCommandData rawCommands = readNativeUICommandMemory(view.contextId); + List? commands; + if (rawCommands.rawMemory.isNotEmpty) { + commands = nativeUICommandToDart(rawCommands.rawMemory, rawCommands.length, view.contextId); + + execUICommands(view, commands); + SchedulerBinding.instance.scheduleFrame(); } - pendingRecalculateTargets.clear(); } diff --git a/webf/lib/src/bridge/ui_command.dart b/webf/lib/src/bridge/ui_command.dart new file mode 100644 index 0000000000..7be3f4c30b --- /dev/null +++ b/webf/lib/src/bridge/ui_command.dart @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'dart:io'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:webf/bridge.dart'; +import 'package:webf/launcher.dart'; +import 'package:webf/dom.dart'; + +class UICommand { + late final UICommandType type; + late final String args; + late final Pointer nativePtr; + late final Pointer nativePtr2; + + @override + String toString() { + return 'UICommand(type: $type, args: $args, nativePtr: $nativePtr, nativePtr2: $nativePtr2)'; + } +} + +const int nodeCreationFlag = 1; +const int nodeMutationFlag = 1 << 2; +const int styleUpdateFlag = 1 << 3; +const int eventFlag = 1 << 4; +const int attributeUpdateFlag = 1 << 5; +const int disposeBindingObjectFlag = 1 << 6; +const int otherOperationFlag = 1 << 7; + +bool isCommandsContainsNodeCreation(int flag) { + return flag & nodeCreationFlag != 0; +} + +bool isCommandsContainsNodeMutation(int flag) { + return flag & nodeMutationFlag != 0; +} + +bool isCommandsContainsStyleUpdate(int flag) { + return flag & styleUpdateFlag != 0; +} + +const int standardUICommandReason = 1; +const int dependentOnElementUICommandReason = 1 << 2; +const int dependentOnLayoutUICommandReason = 1 << 3; + +bool isFlushUICommandReasonDependsOnElement(int reason) { + return reason & dependentOnElementUICommandReason != 0; +} + +bool isFlushUICommandReasonDependsLayoutStyle(int reason) { + return reason & dependentOnLayoutUICommandReason != 0; +} + +// struct UICommandItem { +// int32_t type; // offset: 0 ~ 0.5 +// int32_t args_01_length; // offset: 0.5 ~ 1 +// const uint16_t *string_01;// offset: 1 +// void* nativePtr; // offset: 2 +// void* nativePtr2; // offset: 3 +// }; +const int nativeCommandSize = 4; +const int typeAndArgs01LenMemOffset = 0; +const int args01StringMemOffset = 1; +const int nativePtrMemOffset = 2; +const int native2PtrMemOffset = 3; + +const int commandBufferPrefix = 1; + +bool enableWebFCommandLog = !kReleaseMode && Platform.environment['ENABLE_WEBF_JS_LOG'] == 'true'; + +// We found there are performance bottleneck of reading native memory with Dart FFI API. +// So we align all UI instructions to a whole block of memory, and then convert them into a dart array at one time, +// To ensure the fastest subsequent random access. +List nativeUICommandToDart(List rawMemory, int commandLength, double contextId) { + List results = List.generate(commandLength, (int _i) { + int i = _i * nativeCommandSize; + UICommand command = UICommand(); + + int typeArgs01Combine = rawMemory[i + typeAndArgs01LenMemOffset]; + + // int32_t int32_t + // +-------------+-----------------+ + // | type | args_01_length | + // +-------------+-----------------+ + int args01Length = (typeArgs01Combine >> 32).toSigned(32); + int type = (typeArgs01Combine ^ (args01Length << 32)).toSigned(32); + + command.type = UICommandType.values[type]; + + int args01StringMemory = rawMemory[i + args01StringMemOffset]; + if (args01StringMemory != 0) { + Pointer args_01 = Pointer.fromAddress(args01StringMemory); + command.args = uint16ToString(args_01, args01Length); + malloc.free(args_01); + } else { + command.args = ''; + } + + int nativePtrValue = rawMemory[i + nativePtrMemOffset]; + command.nativePtr = nativePtrValue != 0 ? Pointer.fromAddress(rawMemory[i + nativePtrMemOffset]) : nullptr; + + int nativePtr2Value = rawMemory[i + native2PtrMemOffset]; + command.nativePtr2 = nativePtr2Value != 0 ? Pointer.fromAddress(nativePtr2Value) : nullptr; + return command; + }, growable: false); + + return results; +} + +bool shouldExecUICommands(WebFViewController view, bool isFinishedRecording, Pointer self, int commandFlag, int operationReason) { + if (isFinishedRecording) { + SchedulerBinding.instance.scheduleFrame(); + } + + bool isElementCreation = isCommandsContainsNodeCreation(commandFlag); + bool isElementMutation = isCommandsContainsNodeMutation(commandFlag); + bool isStyleUpdate = isCommandsContainsStyleUpdate(commandFlag); + + bool isDependsOnElement = isFlushUICommandReasonDependsOnElement(operationReason); + if (isDependsOnElement) { + isDependsOnElement = !view.hasBindingObject(self); + } + + bool isDependsOnStyleLayout = isFlushUICommandReasonDependsLayoutStyle(operationReason); + + if (enableWebFCommandLog) { + print(''' +UI COMMAND CONDITION CHECK: + REASON: $operationReason + isElementCreation: $isElementCreation + isElementMutation: $isElementMutation + isDependsOnElement: $isDependsOnElement + isDependsOnStyleLayout: $isDependsOnStyleLayout + '''); + } + return isElementCreation || isElementMutation || isDependsOnElement || isDependsOnStyleLayout || isStyleUpdate; +} + +void execUICommands(WebFViewController view, List commands) { + Map pendingStylePropertiesTargets = {}; + Set pendingRecalculateTargets = {}; + + for(UICommand command in commands) { + UICommandType commandType = command.type; + + if (enableWebFCommandLog) { + String printMsg; + switch(command.type) { + case UICommandType.setStyle: + printMsg = 'nativePtr: ${command.nativePtr} type: ${command.type} key: ${command.args} value: ${nativeStringToString(command.nativePtr2.cast())}'; + break; + case UICommandType.setAttribute: + printMsg = 'nativePtr: ${command.nativePtr} type: ${command.type} key: ${nativeStringToString(command.nativePtr2.cast())} value: ${command.args}'; + break; + case UICommandType.createTextNode: + printMsg = 'nativePtr: ${command.nativePtr} type: ${command.type} data: ${command.args}'; + break; + default: + printMsg = 'nativePtr: ${command.nativePtr} type: ${command.type} args: ${command.args} nativePtr2: ${command.nativePtr2}'; + } + print(printMsg); + } + + if (commandType == UICommandType.startRecordingCommand || commandType == UICommandType.finishRecordingCommand) continue; + + Pointer nativePtr = command.nativePtr; + + try { + switch (commandType) { + case UICommandType.createElement: + view.createElement(nativePtr.cast(), command.args); + break; + case UICommandType.createDocument: + view.initDocument(view, nativePtr.cast()); + break; + case UICommandType.createWindow: + view.initWindow(view, nativePtr.cast()); + break; + case UICommandType.createTextNode: + view.createTextNode(nativePtr.cast(), command.args); + break; + case UICommandType.createComment: + view.createComment(nativePtr.cast()); + break; + case UICommandType.disposeBindingObject: + view.disposeBindingObject(view, nativePtr.cast()); + break; + case UICommandType.addEvent: + Pointer eventListenerOptions = command.nativePtr2.cast(); + view.addEvent(nativePtr.cast(), command.args, + addEventListenerOptions: eventListenerOptions); + break; + case UICommandType.removeEvent: + bool isCapture = command.nativePtr2.address == 1; + view.removeEvent(nativePtr.cast(), command.args, isCapture: isCapture); + break; + case UICommandType.insertAdjacentNode: + view.insertAdjacentNode( + nativePtr.cast(), command.args, command.nativePtr2.cast()); + break; + case UICommandType.removeNode: + view.removeNode(nativePtr.cast()); + break; + case UICommandType.cloneNode: + view.cloneNode(nativePtr.cast(), command.nativePtr2.cast()); + break; + case UICommandType.setStyle: + String value; + if (command.nativePtr2 != nullptr) { + Pointer nativeValue = command.nativePtr2.cast(); + value = nativeStringToString(nativeValue); + freeNativeString(nativeValue); + } else { + value = ''; + } + view.setInlineStyle(nativePtr, command.args, value); + pendingStylePropertiesTargets[nativePtr.address] = true; + break; + case UICommandType.clearStyle: + view.clearInlineStyle(nativePtr); + pendingStylePropertiesTargets[nativePtr.address] = true; + break; + case UICommandType.setAttribute: + Pointer nativeKey = command.nativePtr2.cast(); + String key = nativeStringToString(nativeKey); + freeNativeString(nativeKey); + view.setAttribute(nativePtr.cast(), key, command.args); + pendingRecalculateTargets.add(nativePtr.address); + break; + case UICommandType.removeAttribute: + String key = command.args; + view.removeAttribute(nativePtr, key); + break; + case UICommandType.createDocumentFragment: + view.createDocumentFragment(nativePtr.cast()); + break; + case UICommandType.createSVGElement: + view.createElementNS(nativePtr.cast(), SVG_ELEMENT_URI, command.args); + break; + case UICommandType.createElementNS: + Pointer nativeNameSpaceUri = command.nativePtr2.cast(); + String namespaceUri = nativeStringToString(nativeNameSpaceUri); + freeNativeString(nativeNameSpaceUri); + + view.createElementNS(nativePtr.cast(), namespaceUri, command.args); + break; + default: + break; + } + } catch (e, stack) { + print('$e\n$stack'); + } + } + + // For pending style properties, we needs to flush to render style. + for (int address in pendingStylePropertiesTargets.keys) { + try { + view.flushPendingStyleProperties(address); + } catch (e, stack) { + print('$e\n$stack'); + } + } + pendingStylePropertiesTargets.clear(); + + for (var address in pendingRecalculateTargets) { + try { + view.recalculateStyle(address); + } catch (e, stack) { + print('$e\n$stack'); + } + } + pendingRecalculateTargets.clear(); +} diff --git a/webf/lib/src/css/font_face.dart b/webf/lib/src/css/font_face.dart index 65baa30483..16883d7773 100644 --- a/webf/lib/src/css/font_face.dart +++ b/webf/lib/src/css/font_face.dart @@ -30,7 +30,7 @@ class _Font { } class CSSFontFace { - static Uri? _resolveFontSource(int contextId, String source, String? base) { + static Uri? _resolveFontSource(double contextId, String source, String? base) { WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; base = base ?? controller.url; try { @@ -39,7 +39,7 @@ class CSSFontFace { return null; } } - static void resolveFontFaceRules(CSSFontFaceRule fontFaceRule, int contextId, String? baseHref) async { + static void resolveFontFaceRules(CSSFontFaceRule fontFaceRule, double contextId, String? baseHref) async { CSSStyleDeclaration declaration = fontFaceRule.declarations; String fontFamily = declaration.getPropertyValue('fontFamily'); String url = declaration.getPropertyValue('src'); diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index c9006bc0fe..9987e33ed4 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -75,7 +75,7 @@ class CSSPropertyValue { /// object on the first CSS rule in the document's first stylesheet. /// 3. Via [Window.getComputedStyle()], which exposes the [CSSStyleDeclaration] /// object as a read-only interface. -class CSSStyleDeclaration extends BindingObject { +class CSSStyleDeclaration extends DynamicBindingObject { Element? target; // TODO(yuanyan): defaultStyle should be longhand properties. diff --git a/webf/lib/src/devtools/inspector.dart b/webf/lib/src/devtools/inspector.dart index 0740831aa7..378bbe33e6 100644 --- a/webf/lib/src/devtools/inspector.dart +++ b/webf/lib/src/devtools/inspector.dart @@ -26,7 +26,7 @@ class InspectorServerInit { final int port; final String address; final String bundleURL; - final int contextId; + final double contextId; InspectorServerInit(this.contextId, this.port, this.address, this.bundleURL); } @@ -55,7 +55,7 @@ class InspectorMethodResult { } class InspectorReload { - int contextId; + double contextId; InspectorReload(this.contextId); } diff --git a/webf/lib/src/devtools/server.dart b/webf/lib/src/devtools/server.dart index 768924b1cf..d8cab77bb4 100644 --- a/webf/lib/src/devtools/server.dart +++ b/webf/lib/src/devtools/server.dart @@ -20,14 +20,14 @@ typedef MessageCallback = void Function(Map?); typedef NativeInspectorMessageCallback = Void Function(Pointer rpcSession, Pointer message); typedef DartInspectorMessageCallback = void Function(Pointer rpcSession, Pointer message); -typedef NativeRegisterInspectorMessageCallback = Void Function(Int32 contextId, Pointer rpcSession, +typedef NativeRegisterInspectorMessageCallback = Void Function(Double contextId, Pointer rpcSession, Pointer> inspectorMessageCallback); typedef NativeAttachInspector = Void Function(Int32); typedef DartAttachInspector = void Function(int); -typedef NativeInspectorMessage = Void Function(Int32 contextId, Pointer); -typedef NativePostTaskToUIThread = Void Function(Int32 contextId, Pointer context, Pointer callback); -typedef NativeDispatchInspectorTask = Void Function(Int32 contextId, Pointer context, Pointer callback); -typedef DartDispatchInspectorTask = void Function(int? contextId, Pointer context, Pointer callback); +typedef NativeInspectorMessage = Void Function(Double contextId, Pointer); +typedef NativePostTaskToUIThread = Void Function(Double contextId, Pointer context, Pointer callback); +typedef NativeDispatchInspectorTask = Void Function(Double contextId, Pointer context, Pointer callback); +typedef DartDispatchInspectorTask = void Function(double? contextId, Pointer context, Pointer callback); void serverIsolateEntryPoint(SendPort isolateToMainStream) { ReceivePort mainToIsolateStream = ReceivePort(); diff --git a/webf/lib/src/devtools/service.dart b/webf/lib/src/devtools/service.dart index c28a1fd833..ca40df3419 100644 --- a/webf/lib/src/devtools/service.dart +++ b/webf/lib/src/devtools/service.dart @@ -8,8 +8,8 @@ import 'dart:ffi'; import 'package:webf/webf.dart'; import 'package:webf/devtools.dart'; -typedef NativePostTaskToInspectorThread = Void Function(Int32 contextId, Pointer context, Pointer callback); -typedef DartPostTaskToInspectorThread = void Function(int contextId, Pointer context, Pointer callback); +typedef NativePostTaskToInspectorThread = Void Function(Double contextId, Pointer context, Pointer callback); +typedef DartPostTaskToInspectorThread = void Function(double contextId, Pointer context, Pointer callback); void spawnIsolateInspectorServer(DevToolsService devTool, WebFController controller, {int port = INSPECTOR_DEFAULT_PORT, String? address}) { diff --git a/webf/lib/src/dom/bounding_client_rect.dart b/webf/lib/src/dom/bounding_client_rect.dart index 02afff2631..6536cf4063 100644 --- a/webf/lib/src/dom/bounding_client_rect.dart +++ b/webf/lib/src/dom/bounding_client_rect.dart @@ -3,11 +3,38 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ import 'dart:ffi'; +import 'package:ffi/ffi.dart'; import 'package:webf/bridge.dart'; import 'package:webf/foundation.dart'; -class BoundingClientRect extends BindingObject { +class BindingClientRectData extends Struct { + @Double() + external double x; + + @Double() + external double y; + + @Double() + external double width; + + @Double() + external double height; + + @Double() + external double top; + + @Double() + external double right; + + @Double() + external double bottom; + + @Double() + external double left; +} + +class BoundingClientRect extends StaticBindingObject { static BoundingClientRect zero(BindingContext context) => BoundingClientRect(context: context, x: 0, y: 0, width: 0, height: 0, top: 0, right: 0, bottom: 0, left: 0); final double x; @@ -33,25 +60,24 @@ class BoundingClientRect extends BindingObject { : _pointer = context.pointer, super(context); - final Pointer _pointer; - @override - get pointer => _pointer; + Pointer buildExtraNativeData() { + Pointer extraData = malloc.allocate(sizeOf()); + extraData.ref.width = width; + extraData.ref.height = height; + extraData.ref.x = x; + extraData.ref.y = y; + extraData.ref.left = left; + extraData.ref.top = top; + extraData.ref.right = right; + extraData.ref.bottom = bottom; + return extraData.cast(); + } - @override - void initializeMethods(Map methods) {} + final Pointer _pointer; @override - void initializeProperties(Map properties) { - properties['x'] = BindingObjectProperty(getter: () => x); - properties['y'] = BindingObjectProperty(getter: () => y); - properties['width'] = BindingObjectProperty(getter: () => width); - properties['height'] = BindingObjectProperty(getter: () => height); - properties['left'] = BindingObjectProperty(getter: () => left); - properties['right'] = BindingObjectProperty(getter: () => right); - properties['top'] = BindingObjectProperty(getter: () => top); - properties['bottom'] = BindingObjectProperty(getter: () => bottom); - } + get pointer => _pointer; Map toJSON() { return { diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index d3ae1b057d..f5b3b6b796 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -23,32 +23,49 @@ import 'package:webf/src/foundation/cookie_jar.dart'; /// This class will buffering all the renderObjects who's element are removed from the document tree, and they will be disposed /// in the end of this frame. class _InactiveRenderObjects { - final Set _renderObjects = HashSet(); + Set _renderObjects = {}; + Set _nextDisposeRenderObjects = {}; bool _isScheduled = false; + void _scheduleFrameToFinalizeRenderObjects() { + _isScheduled = true; + /// We needs to wait at least 2 frames to dispose all webf managed renderObjects. + /// All renderObjects managed by WebF should be disposed after Flutter managed renderObjects dispose. + RendererBinding.instance.addPostFrameCallback((timeStamp) { + /// The Flutter framework will move all deactivated elements into _InactiveElement list. + /// They will be disposed in the next frame. + RendererBinding.instance.addPostFrameCallback((timeStamp) { + /// Now the renderObjects managed by Flutter framework are disposed, it's safe to dispose renderObject by our own. + finalizeInactiveRenderObjects(); + _isScheduled = false; + + /// Some pending Objects may be added during this 2 frame waiting. Regrouping them into the waiting list. + if (_nextDisposeRenderObjects.isNotEmpty) { + _renderObjects = _nextDisposeRenderObjects; + _nextDisposeRenderObjects = {}; + _scheduleFrameToFinalizeRenderObjects(); + } + }); + RendererBinding.instance.scheduleFrame(); + }); + RendererBinding.instance.scheduleFrame(); + } + void add(RenderObject? renderObject) { if (renderObject == null) return; if (_renderObjects.isEmpty && !_isScheduled) { - _isScheduled = true; - /// We needs to wait at least 2 frames to dispose all webf managed renderObjects. - /// All renderObjects managed by WebF should be disposed after Flutter managed renderObjects dispose. - RendererBinding.instance.addPostFrameCallback((timeStamp) { - /// The Flutter framework will move all deactivated elements into _InactiveElement list. - /// They will be disposed in the next frame. - RendererBinding.instance.addPostFrameCallback((timeStamp) { - /// Now the renderObjects managed by Flutter framework are disposed, it's safe to dispose renderObject by our own. - finalizeInactiveRenderObjects(); - _isScheduled = false; - }); - RendererBinding.instance.scheduleFrame(); - }); - RendererBinding.instance.scheduleFrame(); + _scheduleFrameToFinalizeRenderObjects(); } assert(!renderObject.debugDisposed!); - _renderObjects.add(renderObject); + + if (!_isScheduled) { + _renderObjects.add(renderObject); + } else { + _nextDisposeRenderObjects.add(renderObject); + } } void finalizeInactiveRenderObjects() { diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 5508a23216..8403c46671 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -305,7 +305,7 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin properties['className'] = BindingObjectProperty(getter: () => className, setter: (value) => className = castToType(value)); properties['classList'] = BindingObjectProperty(getter: () => classList); - properties['dir'] = BindingObjectProperty(getter: () => dir); + properties['dir'] = BindingObjectProperty(getter: () => dir, setter: (value) => {}); } @override @@ -1352,6 +1352,8 @@ abstract class Element extends ContainerNode with ElementBase, ElementEventMixin } void setRenderStyleProperty(String name, value) { + if (renderStyle.target.disposed) return; + dynamic oldValue; switch(name) { diff --git a/webf/lib/src/dom/event_target.dart b/webf/lib/src/dom/event_target.dart index a9054d7d5e..904a2bbad4 100644 --- a/webf/lib/src/dom/event_target.dart +++ b/webf/lib/src/dom/event_target.dart @@ -7,9 +7,9 @@ import 'package:webf/html.dart'; import 'package:webf/dom.dart'; import 'package:webf/foundation.dart'; -typedef EventHandler = void Function(Event event); +typedef EventHandler = Future Function(Event event); -abstract class EventTarget extends BindingObject { +abstract class EventTarget extends DynamicBindingObject { EventTarget(BindingContext? context) : super(context); bool _disposed = false; @@ -63,7 +63,7 @@ abstract class EventTarget extends BindingObject { } @mustCallSuper - void dispatchEvent(Event event) { + Future dispatchEvent(Event event) async { if (_disposed) return; if (this is PseudoElement) { event.target = (this as PseudoElement).parent; @@ -71,12 +71,11 @@ abstract class EventTarget extends BindingObject { event.target = this; } - _handlerCaptureEvent(event); - _dispatchEventInDOM(event); + await _handlerCaptureEvent(event); + await _dispatchEventInDOM(event); } - void _handlerCaptureEvent(Event event) { - - parentEventTarget?._handlerCaptureEvent(event); + Future _handlerCaptureEvent(Event event) async { + await parentEventTarget?._handlerCaptureEvent(event); String eventType = event.type; List? existHandler = _eventCaptureHandlers[eventType]; if (existHandler != null) { @@ -86,7 +85,7 @@ abstract class EventTarget extends BindingObject { // with error, copy the handlers here. try { for (EventHandler handler in [...existHandler]) { - handler(event); + await handler(event); } } catch (e, stack) { print('$e\n$stack'); @@ -95,7 +94,7 @@ abstract class EventTarget extends BindingObject { } } // Refs: https://github.com/WebKit/WebKit/blob/main/Source/WebCore/dom/EventDispatcher.cpp#L85 - void _dispatchEventInDOM(Event event) { + Future _dispatchEventInDOM(Event event) async { // TODO: Invoke capturing event listeners in the reverse order. String eventType = event.type; @@ -107,7 +106,7 @@ abstract class EventTarget extends BindingObject { // with error, copy the handlers here. try { for (EventHandler handler in [...existHandler]) { - handler(event); + await handler(event); } } catch (e, stack) { print('$e\n$stack'); @@ -117,7 +116,7 @@ abstract class EventTarget extends BindingObject { // Invoke bubbling event listeners. if (event.bubbles && !event.propagationStopped) { - parentEventTarget?._dispatchEventInDOM(event); + await parentEventTarget?._dispatchEventInDOM(event); } } diff --git a/webf/lib/src/dom/node.dart b/webf/lib/src/dom/node.dart index daf1f7dc44..cdd10e3daa 100644 --- a/webf/lib/src/dom/node.dart +++ b/webf/lib/src/dom/node.dart @@ -353,7 +353,7 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa } @override - void dispatchEvent(Event event) { + Future dispatchEvent(Event event) async { if (disposed) return; super.dispatchEvent(event); } diff --git a/webf/lib/src/dom/screen.dart b/webf/lib/src/dom/screen.dart index ff1aebae1b..626fb3ed51 100644 --- a/webf/lib/src/dom/screen.dart +++ b/webf/lib/src/dom/screen.dart @@ -2,27 +2,41 @@ * Copyright (C) 2022-present Alibaba Inc. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; import 'dart:ui'; import 'package:webf/foundation.dart'; import 'package:webf/bridge.dart'; import 'package:webf/launcher.dart'; +class ScreenData extends Struct { + @Int64() + external int availWidth; + + @Int64() + external int availHeight; + + @Int64() + external int width; + + @Int64() + external int height; +} + // As its name suggests, the Screen interface represents information about the screen of the output device. // https://drafts.csswg.org/cssom-view/#the-screen-interface -class Screen extends BindingObject { +class Screen extends StaticBindingObject { final FlutterView currentView; - Screen(int contextId, this.currentView, WebFViewController view) : super(BindingContext(view, contextId, allocateNewBindingObject())); - - @override - void initializeMethods(Map methods) { - } + Screen(double contextId, this.currentView, WebFViewController view) : super(BindingContext(view, contextId, allocateNewBindingObject())); @override - void initializeProperties(Map properties) { - properties['availWidth'] = BindingObjectProperty(getter: () => availWidth); - properties['availHeight'] = BindingObjectProperty(getter: () => availHeight); - properties['width'] = BindingObjectProperty(getter: () => width); - properties['height'] = BindingObjectProperty(getter: () => height); + Pointer buildExtraNativeData() { + Pointer extraData = malloc.allocate(sizeOf()); + extraData.ref.width = currentView.physicalSize.width ~/ currentView.devicePixelRatio; + extraData.ref.height = currentView.physicalSize.height ~/ currentView.devicePixelRatio; + extraData.ref.availWidth = currentView.physicalSize.width ~/ currentView.devicePixelRatio; + extraData.ref.availHeight = currentView.physicalSize.height ~/ currentView.devicePixelRatio; + return extraData.cast(); } // The availWidth attribute must return the width of the Web-exposed available screen area. diff --git a/webf/lib/src/dom/window.dart b/webf/lib/src/dom/window.dart index be91e54b66..741c4da598 100644 --- a/webf/lib/src/dom/window.dart +++ b/webf/lib/src/dom/window.dart @@ -98,12 +98,12 @@ class Window extends EventTarget { } @override - void dispatchEvent(Event event) { + Future dispatchEvent(Event event) async { // Events such as EVENT_DOM_CONTENT_LOADED need to ensure that listeners are flushed and registered. if (contextId != null && event.type == EVENT_DOM_CONTENT_LOADED || event.type == EVENT_LOAD || event.type == EVENT_ERROR) { - flushUICommandWithContextId(contextId!); + flushUICommandWithContextId(contextId!, pointer!, dependentOnElementUICommandReason | dependentOnLayoutUICommandReason); } super.dispatchEvent(event); } diff --git a/webf/lib/src/foundation/binding.dart b/webf/lib/src/foundation/binding.dart index a2a1ef8f81..91b1570395 100644 --- a/webf/lib/src/foundation/binding.dart +++ b/webf/lib/src/foundation/binding.dart @@ -2,8 +2,8 @@ * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +import 'dart:async'; import 'dart:ffi'; -import 'dart:collection'; import 'package:collection/collection.dart'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart'; @@ -14,7 +14,7 @@ import 'package:webf/launcher.dart'; typedef BindingObjectOperation = void Function(WebFViewController? view, BindingObject bindingObject); class BindingContext { - final int contextId; + final double contextId; final WebFViewController view; final Pointer pointer; @@ -58,12 +58,9 @@ abstract class BindingObject extends Iterable { static BindingObjectOperation? bind; static BindingObjectOperation? unbind; - // To make sure same kind of WidgetElement only sync once. - static final Map _alreadySyncWidgetElements = {}; - final BindingContext? _context; - int? get contextId => _context?.contextId; + double? get contextId => _context?.contextId; final WebFViewController? _ownerView; WebFViewController get ownerView => _ownerView!; @@ -71,50 +68,49 @@ abstract class BindingObject extends Iterable { BindingObject([BindingContext? context]) : _context = context, _ownerView = context?.view { _bind(_ownerView); - initializeProperties(_properties); - initializeMethods(_methods); + } - if (this is WidgetElement && !_alreadySyncWidgetElements.containsKey(runtimeType)) { - bool success = _syncPropertiesAndMethodsToNativeSlow(); - if (success) { - _alreadySyncWidgetElements[runtimeType] = true; - } + // Bind dart side object method to receive invoking from native side. + void _bind(WebFViewController? ownerView) { + if (bind != null) { + bind!(ownerView, this); } } - bool _syncPropertiesAndMethodsToNativeSlow() { - assert(pointer != null); - if (pointer!.ref.invokeBindingMethodFromDart == nullptr) return false; + void _unbind(WebFViewController? ownerView) { + if (unbind != null) { + unbind!(ownerView, this); + } + } - List properties = _properties.keys.toList(growable: false); - List syncMethods = []; - List asyncMethods = []; + @override + Iterator get iterator => Iterable.empty().iterator; - _methods.forEach((key, method) { - if (method is BindingObjectMethodSync) { - syncMethods.add(key); - } else if (method is AsyncBindingObjectMethod) { - asyncMethods.add(key); - } - }); + @mustCallSuper + void dispose(); +} + +abstract class StaticBindingObject extends BindingObject { + StaticBindingObject(BindingContext context): super(context) { + context.pointer.ref.extra = buildExtraNativeData(); + } - Pointer arguments = malloc.allocate(sizeOf() * 3); - toNativeValue(arguments.elementAt(0), properties); - toNativeValue(arguments.elementAt(1), syncMethods); - toNativeValue(arguments.elementAt(2), asyncMethods); + Pointer buildExtraNativeData(); - DartInvokeBindingMethodsFromDart f = pointer!.ref.invokeBindingMethodFromDart.asFunction(); - Pointer returnValue = malloc.allocate(sizeOf()); + @override + void dispose() { + malloc.free(pointer!.ref.extra); + } +} - Pointer method = malloc.allocate(sizeOf()); - toNativeValue(method, 'syncPropertiesAndMethods'); - f(pointer!, returnValue, method, 3, arguments, {}); - malloc.free(arguments); - return fromNativeValue(ownerView, returnValue) == true; +abstract class DynamicBindingObject extends BindingObject { + DynamicBindingObject([BindingContext? context]): super(context) { + initializeProperties(_properties); + initializeMethods(_methods); } - final SplayTreeMap _properties = SplayTreeMap(); - final SplayTreeMap _methods = SplayTreeMap(); + final Map _properties = {}; + final Map _methods = {}; @mustCallSuper void initializeProperties(Map properties); @@ -122,17 +118,24 @@ abstract class BindingObject extends Iterable { @mustCallSuper void initializeMethods(Map methods); - // Bind dart side object method to receive invoking from native side. - void _bind(WebFViewController? ownerView) { - if (bind != null) { - bind!(ownerView, this); - } - } + void nativeGetPropertiesAndMethods(Pointer data) async { + assert(pointer != null); - void _unbind(WebFViewController? ownerView) { - if (unbind != null) { - unbind!(ownerView, this); - } + List properties = _properties.keys.toList(growable: false); + List syncMethods = []; + List asyncMethods = []; + + _methods.forEach((key, method) { + if (method is BindingObjectMethodSync) { + syncMethods.add(key); + } else if (method is AsyncBindingObjectMethod) { + asyncMethods.add(key); + } + }); + + toNativeValue(data.elementAt(0), properties); + toNativeValue(data.elementAt(1), syncMethods); + toNativeValue(data.elementAt(2), asyncMethods); } // Call a method, eg: @@ -150,9 +153,6 @@ abstract class BindingObject extends Iterable { return null; } - @override - Iterator get iterator => Iterable.empty().iterator; - dynamic _invokeBindingMethodAsync(String method, List args) { BindingObjectMethod? fn = _methods[method]; if (fn == null) { @@ -160,32 +160,32 @@ abstract class BindingObject extends Iterable { } if (fn is AsyncBindingObjectMethod) { - int contextId = args[0]; + double contextId = args[0]; // Async callback should hold a context to store the current execution environment. Pointer callbackContext = (args[1] as Pointer).cast(); DartAsyncAnonymousFunctionCallback callback = - (args[2] as Pointer).cast>().asFunction(); + (args[2] as Pointer).cast>().asFunction(); List functionArguments = args.sublist(3); Future p = fn.call(functionArguments); p.then((result) { Stopwatch? stopwatch; - if (isEnabledLog) { + if (enableWebFCommandLog) { stopwatch = Stopwatch()..start(); } Pointer nativeValue = malloc.allocate(sizeOf()); toNativeValue(nativeValue, result, this); callback(callbackContext, nativeValue, contextId, nullptr); - if (isEnabledLog) { + if (enableWebFCommandLog) { print('AsyncAnonymousFunction call resolved callback: $method arguments:[$result] time: ${stopwatch!.elapsedMicroseconds}us'); } }).catchError((e, stack) { String errorMessage = '$e\n$stack'; Stopwatch? stopwatch; - if (isEnabledLog) { + if (enableWebFCommandLog) { stopwatch = Stopwatch()..start(); } callback(callbackContext, nullptr, contextId, errorMessage.toNativeUtf8()); - if (isEnabledLog) { + if (enableWebFCommandLog) { print('AsyncAnonymousFunction call rejected callback: $method, arguments:[$errorMessage] time: ${stopwatch!.elapsedMicroseconds}us'); } }); @@ -194,7 +194,7 @@ abstract class BindingObject extends Iterable { return null; } - @mustCallSuper + @override void dispose() async { _unbind(_ownerView); _properties.clear(); @@ -205,16 +205,16 @@ abstract class BindingObject extends Iterable { dynamic getterBindingCall(BindingObject bindingObject, List args) { assert(args.length == 1); - BindingObjectProperty? property = bindingObject._properties[args[0]]; + BindingObjectProperty? property = (bindingObject as DynamicBindingObject)._properties[args[0]]; Stopwatch? stopwatch; - if (isEnabledLog && property != null) { + if (enableWebFCommandLog && property != null) { stopwatch = Stopwatch()..start(); } if (property != null) { dynamic result = property.getter(); - if (isEnabledLog) { + if (enableWebFCommandLog) { print('$bindingObject getBindingProperty key: ${args[0]} result: ${property.getter()} time: ${stopwatch!.elapsedMicroseconds}us'); } return result; @@ -224,13 +224,13 @@ dynamic getterBindingCall(BindingObject bindingObject, List args) { dynamic setterBindingCall(BindingObject bindingObject, List args) { assert(args.length == 2); - if (isEnabledLog) { + if (enableWebFCommandLog) { print('$bindingObject setBindingProperty key: ${args[0]} value: ${args[1]}'); } String key = args[0]; dynamic value = args[1]; - BindingObjectProperty? property = bindingObject._properties[key]; + BindingObjectProperty? property = (bindingObject as DynamicBindingObject)._properties[key]; if (property != null && property.setter != null) { property.setter!(value); @@ -247,11 +247,12 @@ dynamic setterBindingCall(BindingObject bindingObject, List args) { } dynamic getPropertyNamesBindingCall(BindingObject bindingObject, List args) { - List properties = bindingObject._properties.keys.toList(); + assert(bindingObject is DynamicBindingObject); + List properties = (bindingObject as DynamicBindingObject)._properties.keys.toList(); List methods = bindingObject._methods.keys.toList(); properties.addAll(methods); - if (isEnabledLog) { + if (enableWebFCommandLog) { print('$bindingObject getPropertyNamesBindingCall value: $properties'); } @@ -260,25 +261,26 @@ dynamic getPropertyNamesBindingCall(BindingObject bindingObject, List a dynamic invokeBindingMethodSync(BindingObject bindingObject, List args) { Stopwatch? stopwatch; - if (isEnabledLog) { + if (enableWebFCommandLog) { stopwatch = Stopwatch()..start(); } - dynamic result = bindingObject._invokeBindingMethodSync(args[0], args.slice(1)); - if (isEnabledLog) { + assert(bindingObject is DynamicBindingObject); + dynamic result = (bindingObject as DynamicBindingObject)._invokeBindingMethodSync(args[0], args.slice(1)); + if (enableWebFCommandLog) { print('$bindingObject invokeBindingMethodSync method: ${args[0]} args: ${args.slice(1)} time: ${stopwatch!.elapsedMilliseconds}ms'); } return result; } dynamic invokeBindingMethodAsync(BindingObject bindingObject, List args) { - if (isEnabledLog) { + if (enableWebFCommandLog) { print('$bindingObject invokeBindingMethodSync method: ${args[0]} args: ${args.slice(1)}'); } - return bindingObject._invokeBindingMethodAsync(args[0], args.slice(1)); + return (bindingObject as DynamicBindingObject)._invokeBindingMethodAsync(args[0], args.slice(1)); } // This function receive calling from binding side. -void invokeBindingMethodFromNativeImpl(int contextId, Pointer nativeBindingObject, +void invokeBindingMethodFromNativeImpl(double contextId, Pointer nativeBindingObject, Pointer returnValue, Pointer nativeMethod, int argc, Pointer argv) { WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; dynamic method = fromNativeValue(controller.view, nativeMethod); @@ -299,11 +301,11 @@ void invokeBindingMethodFromNativeImpl(int contextId, Pointer putObject(Uint8List codeBytes, Uint8List bytes) async { final String key = _getCacheHash(codeBytes); final Directory cacheDirectory = await getCacheDirectory(); diff --git a/webf/lib/src/foundation/http_client_request.dart b/webf/lib/src/foundation/http_client_request.dart index b1ca18680d..54c35936bc 100644 --- a/webf/lib/src/foundation/http_client_request.dart +++ b/webf/lib/src/foundation/http_client_request.dart @@ -104,7 +104,7 @@ class ProxyHttpClientRequest extends HttpClientRequest { @override Future close() async { - int? contextId = WebFHttpOverrides.getContextHeader(headers); + double? contextId = WebFHttpOverrides.getContextHeader(headers); HttpClientRequest request = this; if (contextId != null) { diff --git a/webf/lib/src/foundation/http_overrides.dart b/webf/lib/src/foundation/http_overrides.dart index ed92a607a2..ddf1f1aae3 100644 --- a/webf/lib/src/foundation/http_overrides.dart +++ b/webf/lib/src/foundation/http_overrides.dart @@ -19,35 +19,35 @@ class WebFHttpOverrides extends HttpOverrides { return _instance!; } - static int? getContextHeader(HttpHeaders headers) { + static double? getContextHeader(HttpHeaders headers) { String? intVal = headers.value(HttpHeaderContext); if (intVal == null) { return null; } - return int.tryParse(intVal); + return double.tryParse(intVal); } - static void setContextHeader(HttpHeaders headers, int contextId) { + static void setContextHeader(HttpHeaders headers, double contextId) { headers.set(HttpHeaderContext, contextId.toString()); } final HttpOverrides? parentHttpOverrides = HttpOverrides.current; - final Map _contextIdToHttpClientInterceptorMap = {}; + final Map _contextIdToHttpClientInterceptorMap = {}; - void registerWebFContext(int contextId, HttpClientInterceptor httpClientInterceptor) { + void registerWebFContext(double contextId, HttpClientInterceptor httpClientInterceptor) { _contextIdToHttpClientInterceptorMap[contextId] = httpClientInterceptor; } - bool unregisterWebFContext(int contextId) { + bool unregisterWebFContext(double contextId) { // Returns true if [value] was in the map, false otherwise. return _contextIdToHttpClientInterceptorMap.remove(contextId) != null; } - bool hasInterceptor(int contextId) { + bool hasInterceptor(double contextId) { return _contextIdToHttpClientInterceptorMap.containsKey(contextId); } - HttpClientInterceptor getInterceptor(int contextId) { + HttpClientInterceptor getInterceptor(double contextId) { return _contextIdToHttpClientInterceptorMap[contextId]!; } @@ -77,7 +77,7 @@ class WebFHttpOverrides extends HttpOverrides { } } -WebFHttpOverrides setupHttpOverrides(HttpClientInterceptor? httpClientInterceptor, {required int contextId}) { +WebFHttpOverrides setupHttpOverrides(HttpClientInterceptor? httpClientInterceptor, {required double contextId}) { final WebFHttpOverrides httpOverrides = WebFHttpOverrides.instance(); if (httpClientInterceptor != null) { @@ -98,8 +98,8 @@ String getOrigin(Uri uri) { } // @TODO: Remove controller dependency. -Uri getEntrypointUri(int? contextId) { +Uri getEntrypointUri(double? contextId) { WebFController? controller = WebFController.getControllerOfJSContextId(contextId); String url = controller?.url ?? ''; - return Uri.tryParse(url) ?? WebFController.fallbackBundleUri(contextId ?? 0); + return Uri.tryParse(url) ?? WebFController.fallbackBundleUri(contextId ?? 0.0); } diff --git a/webf/lib/src/foundation/ui_command_iterator.dart b/webf/lib/src/foundation/ui_command_iterator.dart new file mode 100644 index 0000000000..53f4581eab --- /dev/null +++ b/webf/lib/src/foundation/ui_command_iterator.dart @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'package:webf/bridge.dart'; + +class UICommandIterator extends Iterator { + final List> _commandChunks = []; + int _chunkIndex = 0; + int _commandIndex = 0; + int _commandSize = 0; + int commandFlag = 0; + + void addCommandChunks(List chunk, int flag) { + _commandChunks.add(chunk); + _commandSize += chunk.length; + commandFlag = commandFlag | flag; + } + + bool isEmpty() { + return _commandSize == 0; + } + + void clear() { + _commandChunks.clear(); + _chunkIndex = 0; + _commandIndex = 0; + commandFlag = 0; + _commandSize = 0; + } + + int size() { + return _commandSize; + } + + @override + UICommand? get current { + if (_commandChunks.isEmpty) return null; + + List currentCommandChunk = _commandChunks[_chunkIndex]; + if (currentCommandChunk.isEmpty) return null; + + UICommand result = currentCommandChunk[_commandIndex]; + + int nextCommandIndex = _commandIndex + 1; + if (nextCommandIndex == currentCommandChunk.length) { + _commandIndex = 0; + _chunkIndex++; + } else { + _commandIndex++; + } + + return result; + } + + @override + bool moveNext() { + if (_commandChunks.isEmpty) return false; + if (_chunkIndex >= _commandChunks.length) { + return false; + } + + List currentCommandChunk = _commandChunks[_chunkIndex]; + if (currentCommandChunk.isEmpty) return false; + if (_commandIndex < currentCommandChunk.length) { + return true; + } + return false; + } +} + +class UICommandIterable extends Iterable { + final UICommandIterator _iterator; + UICommandIterable(this._iterator); + + @override + Iterator get iterator => _iterator; +} diff --git a/webf/lib/src/geometry/dom_matrix_readonly.dart b/webf/lib/src/geometry/dom_matrix_readonly.dart index 306a247d43..b66271956a 100644 --- a/webf/lib/src/geometry/dom_matrix_readonly.dart +++ b/webf/lib/src/geometry/dom_matrix_readonly.dart @@ -4,7 +4,7 @@ import 'package:webf/foundation.dart'; -class DOMMatrixReadonly extends BindingObject { +class DOMMatrixReadonly extends DynamicBindingObject { DOMMatrixReadonly(BindingContext context, List domMatrixInit): super(context); @override diff --git a/webf/lib/src/html/a.dart b/webf/lib/src/html/a.dart index 4126a74903..b08d59d344 100644 --- a/webf/lib/src/html/a.dart +++ b/webf/lib/src/html/a.dart @@ -13,7 +13,7 @@ class HTMLAnchorElement extends Element { addEventListener(EVENT_CLICK, _handleClick); } - void _handleClick(Event event) { + Future _handleClick(Event event) async { String? href = attributes['href']; if (href != null && href.isNotEmpty) { String baseUrl = ownerDocument.controller.url; diff --git a/webf/lib/src/html/canvas/canvas_context.dart b/webf/lib/src/html/canvas/canvas_context.dart index 221bcbd579..cc0d1c77ef 100644 --- a/webf/lib/src/html/canvas/canvas_context.dart +++ b/webf/lib/src/html/canvas/canvas_context.dart @@ -135,7 +135,7 @@ abstract class CanvasImageData { } // ignore: one_member_abstracts -class CanvasGradient extends BindingObject { +class CanvasGradient extends DynamicBindingObject { CanvasGradient(BindingContext context, this.ownerCanvasElement) : _pointer = context.pointer, super(context); @@ -167,7 +167,7 @@ class CanvasGradient extends BindingObject { } // ignore: one_member_abstracts -class CanvasPattern extends BindingObject { +class CanvasPattern extends DynamicBindingObject { CanvasPattern(BindingContext context, CanvasImageSource image, String repetition) : _pointer = context.pointer, _image = image, diff --git a/webf/lib/src/html/canvas/canvas_context_2d.dart b/webf/lib/src/html/canvas/canvas_context_2d.dart index 888b0b2eb6..56d9c78893 100644 --- a/webf/lib/src/html/canvas/canvas_context_2d.dart +++ b/webf/lib/src/html/canvas/canvas_context_2d.dart @@ -45,7 +45,7 @@ enum FillStyleType { string, canvasGradient } typedef CanvasAction = void Function(Canvas, Size); -class CanvasRenderingContext2D extends BindingObject { +class CanvasRenderingContext2D extends DynamicBindingObject { CanvasRenderingContext2D(BindingContext context, this.canvas) : _pointer = context.pointer, super(context); @@ -646,7 +646,7 @@ class CanvasRenderingContext2D extends BindingObject { addAction((Canvas canvas, Size size) { if (imgElement?.image == null) return; - + Image img = imgElement!.image!; // ctx.drawImage(image, dx, dy); if (argumentCount == 3) { diff --git a/webf/lib/src/html/html.dart b/webf/lib/src/html/html.dart index 3b808be452..a4d3c32ebd 100644 --- a/webf/lib/src/html/html.dart +++ b/webf/lib/src/html/html.dart @@ -18,7 +18,7 @@ class HTMLElement extends Element { Map get defaultStyle => _defaultStyle; @override - void dispatchEvent(Event event) { + Future dispatchEvent(Event event) async { // Scroll event proxy to document. if (event.type == EVENT_SCROLL) { // https://www.w3.org/TR/2014/WD-DOM-Level-3-Events-20140925/#event-type-scroll diff --git a/webf/lib/src/html/pseudo.dart b/webf/lib/src/html/pseudo.dart index 60e36e1bb3..4923b16f67 100644 --- a/webf/lib/src/html/pseudo.dart +++ b/webf/lib/src/html/pseudo.dart @@ -18,6 +18,9 @@ class PseudoElement extends Element { final PseudoKind kind; final Element parent; + @override + String tagName = 'Pseudo'; + PseudoElement(this.kind, this.parent, [BindingContext? context]) : super(context); @override diff --git a/webf/lib/src/html/script.dart b/webf/lib/src/html/script.dart index 6d115bf27e..fbe0e434fa 100644 --- a/webf/lib/src/html/script.dart +++ b/webf/lib/src/html/script.dart @@ -27,18 +27,18 @@ enum ScriptReadyState { loading, interactive, complete } typedef ScriptExecution = Future Function(bool async); class ScriptRunner { - ScriptRunner(Document document, int contextId) + ScriptRunner(Document document, double contextId) : _document = document, _contextId = contextId; final Document _document; - final int _contextId; + final double _contextId; final List _syncScriptTasks = []; // Indicate the sync pending scripts. int _resolvingCount = 0; - static Future _evaluateScriptBundle(int contextId, WebFBundle bundle, {bool async = false}) async { + static Future _evaluateScriptBundle(double contextId, WebFBundle bundle, {bool async = false}) async { // Evaluate bundle. if (bundle.isJavascript) { assert(isValidUTF8String(bundle.data!), 'The JavaScript codes should be in UTF-8 encoding format'); @@ -47,7 +47,7 @@ class ScriptRunner { throw FlutterError('Script code are not valid to evaluate.'); } } else if (bundle.isBytecode) { - bool result = evaluateQuickjsByteCode(contextId, bundle.data!); + bool result = await evaluateQuickjsByteCode(contextId, bundle.data!); if (!result) { throw FlutterError('Bytecode are not valid to execute.'); } @@ -261,7 +261,7 @@ class ScriptElement extends Element { } void _fetchAndExecuteSource() async { - int? contextId = ownerDocument.contextId; + double? contextId = ownerDocument.contextId; if (contextId == null) return; // Must if (src.isNotEmpty && @@ -283,7 +283,7 @@ class ScriptElement extends Element { @override void connectedCallback() async { super.connectedCallback(); - int? contextId = ownerDocument.contextId; + double? contextId = ownerDocument.contextId; if (contextId == null) return; if (src.isNotEmpty) { _fetchAndExecuteSource(); diff --git a/webf/lib/src/html/semantics_text.dart b/webf/lib/src/html/semantics_text.dart index f42bbface2..ddb9bfb7d6 100644 --- a/webf/lib/src/html/semantics_text.dart +++ b/webf/lib/src/html/semantics_text.dart @@ -68,7 +68,7 @@ class BRElement extends Element { @override RenderBox createRenderer() { - return _renderLineBreak ??= RenderLineBreak(renderStyle); + return _renderLineBreak = RenderLineBreak(renderStyle); } } diff --git a/webf/lib/src/launcher/controller.dart b/webf/lib/src/launcher/controller.dart index eaffac889b..550fd5a770 100644 --- a/webf/lib/src/launcher/controller.dart +++ b/webf/lib/src/launcher/controller.dart @@ -62,8 +62,8 @@ abstract class DevToolsService { /// More detail see [InspectPageModule.handleReloadPage]. static DevToolsService? prevDevTools; - static final Map _contextDevToolMap = {}; - static DevToolsService? getDevToolOfContextId(int contextId) { + static final Map _contextDevToolMap = {}; + static DevToolsService? getDevToolOfContextId(double contextId) { return _contextDevToolMap[contextId]; } @@ -128,6 +128,9 @@ class WebFViewController implements WidgetsBindingObserver { List? initialCookies; + final List> pendingUICommands = []; + // final UICommandIterator pendingUICommands = UICommandIterator(); + double _viewportWidth; double get viewportWidth => _viewportWidth; set viewportWidth(double value) { @@ -147,29 +150,33 @@ class WebFViewController implements WidgetsBindingObserver { } Color? background; + WebFThread runningThread; WebFViewController(this._viewportWidth, this._viewportHeight, {this.background, this.enableDebug = false, required this.rootController, + required this.runningThread, this.navigationDelegate, this.gestureListener, this.initialCookies, // Viewport won't change when kraken page reload, should reuse previous page's viewportBox. RenderViewportBox? originalViewport}) { - if (enableDebug) { - debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; - debugPaintSizeEnabled = true; - } - BindingBridge.setup(); - _contextId = initBridge(this); - if (originalViewport != null) { viewport = originalViewport; } else { viewport = RenderViewportBox( background: background, viewportSize: ui.Size(viewportWidth, viewportHeight), controller: rootController); } + } + + Future initialize() async { + if (enableDebug) { + debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; + debugPaintSizeEnabled = true; + } + BindingBridge.setup(); + _contextId = await initBridge(this, runningThread); _setupObserver(); @@ -178,7 +185,7 @@ class WebFViewController implements WidgetsBindingObserver { // Wait viewport mounted on the outside renderObject tree. Future.microtask(() { // Execute UICommand.createDocument and UICommand.createWindow to initialize window and document. - flushUICommand(this); + flushUICommand(this, nullptr, dependentOnElementUICommandReason | dependentOnLayoutUICommandReason); }); SchedulerBinding.instance.addPostFrameCallback(_postFrameCallback); @@ -186,7 +193,7 @@ class WebFViewController implements WidgetsBindingObserver { void _postFrameCallback(Duration timeStamp) { if (disposed) return; - flushUICommand(this); + flushUICommand(this, window.pointer!, standardUICommandReason); SchedulerBinding.instance.addPostFrameCallback(_postFrameCallback); } @@ -210,8 +217,8 @@ class WebFViewController implements WidgetsBindingObserver { } // Index value which identify javascript runtime context. - late int _contextId; - int get contextId => _contextId; + late double _contextId; + double get contextId => _contextId; // Enable print debug message when rendering. bool enableDebug; @@ -238,19 +245,19 @@ class WebFViewController implements WidgetsBindingObserver { if (gestureListener != null) { GestureListener listener = gestureListener!; if (listener.onTouchStart != null) { - document.addEventListener(EVENT_TOUCH_START, (Event event) => listener.onTouchStart!(event as TouchEvent)); + document.addEventListener(EVENT_TOUCH_START, (Event event) async => listener.onTouchStart!(event as TouchEvent)); } if (listener.onTouchMove != null) { - document.addEventListener(EVENT_TOUCH_MOVE, (Event event) => listener.onTouchMove!(event as TouchEvent)); + document.addEventListener(EVENT_TOUCH_MOVE, (Event event) async => listener.onTouchMove!(event as TouchEvent)); } if (listener.onTouchEnd != null) { - document.addEventListener(EVENT_TOUCH_END, (Event event) => listener.onTouchEnd!(event as TouchEvent)); + document.addEventListener(EVENT_TOUCH_END, (Event event) async => listener.onTouchEnd!(event as TouchEvent)); } if (listener.onDrag != null) { - document.addEventListener(EVENT_DRAG, (Event event) => listener.onDrag!(event as GestureEvent)); + document.addEventListener(EVENT_DRAG, (Event event) async => listener.onDrag!(event as GestureEvent)); } } } @@ -260,7 +267,7 @@ class WebFViewController implements WidgetsBindingObserver { _registerPlatformBrightnessChange(); // Blur input element when new input focused. - window.addEventListener(EVENT_CLICK, (event) { + window.addEventListener(EVENT_CLICK, (event) async { if (event.target is Element) { Element? focusedElement = document.focusedElement; if (focusedElement != null && focusedElement != event.target) { @@ -279,7 +286,7 @@ class WebFViewController implements WidgetsBindingObserver { document.cookie.clearCookie(); } - void evaluateJavaScripts(String code) async { + Future evaluateJavaScripts(String code) async { assert(!_disposed, 'WebF have already disposed'); List data = utf8.encode(code); await evaluateScripts(_contextId, Uint8List.fromList(data)); @@ -303,7 +310,8 @@ class WebFViewController implements WidgetsBindingObserver { } // Dispose controller and recycle all resources. - void dispose() { + Future dispose() async { + await waitingSyncTaskComplete(contextId); _disposed = true; debugDOMTreeChanged = null; @@ -313,7 +321,8 @@ class WebFViewController implements WidgetsBindingObserver { // Should clear previous page cached ui commands clearUICommand(_contextId); - disposePage(_contextId); + await disposePage(runningThread is FlutterUIThread, _contextId); + pendingUICommands.clear(); clearCssLength(); @@ -728,7 +737,7 @@ class WebFModuleController with TimerMixin, ScheduleFrameMixin { late ModuleManager _moduleManager; ModuleManager get moduleManager => _moduleManager; - WebFModuleController(WebFController controller, int contextId) { + WebFModuleController(WebFController controller, double contextId) { _moduleManager = ModuleManager(controller, contextId); } @@ -744,12 +753,12 @@ class WebFModuleController with TimerMixin, ScheduleFrameMixin { } class WebFController { - static final Map _controllerMap = {}; - static final Map _nameIdMap = {}; + static final Map _controllerMap = {}; + static final Map _nameIdMap = {}; UriParser? uriParser; - static WebFController? getControllerOfJSContextId(int? contextId) { + static WebFController? getControllerOfJSContextId(double? contextId) { if (!_controllerMap.containsKey(contextId)) { return null; } @@ -757,13 +766,13 @@ class WebFController { return _controllerMap[contextId]; } - static Map getControllerMap() { + static Map getControllerMap() { return _controllerMap; } static WebFController? getControllerOfName(String name) { if (!_nameIdMap.containsKey(name)) return null; - int? contextId = _nameIdMap[name]; + double? contextId = _nameIdMap[name]; return getControllerOfJSContextId(contextId); } @@ -806,7 +815,7 @@ class WebFController { set name(String? value) { if (value == null) return; if (_name != null) { - int? contextId = _nameIdMap[_name]; + double? contextId = _nameIdMap[_name]; _nameIdMap.remove(_name); _nameIdMap[value] = contextId!; } @@ -831,6 +840,10 @@ class WebFController { // The kraken view entrypoint bundle. WebFBundle? _entrypoint; + final WebFThread runningThread; + + Completer controlledInitCompleter = Completer(); + WebFController( String? name, double viewportWidth, @@ -843,6 +856,7 @@ class WebFController { WebFNavigationDelegate? navigationDelegate, WebFMethodChannel? methodChannel, WebFBundle? entrypoint, + WebFThread? runningThread, this.onCustomElementAttached, this.onCustomElementDetached, this.onLoad, @@ -858,6 +872,7 @@ class WebFController { this.resizeToAvoidBottomInsets = true, }) : _name = name, _entrypoint = entrypoint, + runningThread = runningThread ?? DedicatedThread(), _gestureListener = gestureListener { _initializePreloadBundle(); @@ -871,38 +886,43 @@ class WebFController { background: background, enableDebug: enableDebug, rootController: this, + runningThread: this.runningThread, navigationDelegate: navigationDelegate ?? WebFNavigationDelegate(), gestureListener: _gestureListener, initialCookies: initialCookies ); - final int contextId = _view.contextId; + _view.initialize().then((_) { + final double contextId = _view.contextId; - _module = WebFModuleController(this, contextId); + _module = WebFModuleController(this, contextId); - if (entrypoint != null) { - HistoryModule historyModule = module.moduleManager.getModule('History')!; - historyModule.add(entrypoint); - } + if (entrypoint != null) { + HistoryModule historyModule = module.moduleManager.getModule('History')!; + historyModule.add(entrypoint); + } - assert(!_controllerMap.containsKey(contextId), 'found exist contextId of WebFController, contextId: $contextId'); - _controllerMap[contextId] = this; - assert(!_nameIdMap.containsKey(name), 'found exist name of WebFController, name: $name'); - if (name != null) { - _nameIdMap[name] = contextId; - } + assert(!_controllerMap.containsKey(contextId), 'found exist contextId of WebFController, contextId: $contextId'); + _controllerMap[contextId] = this; + assert(!_nameIdMap.containsKey(name), 'found exist name of WebFController, name: $name'); + if (name != null) { + _nameIdMap[name] = contextId; + } - setupHttpOverrides(httpClientInterceptor, contextId: contextId); + setupHttpOverrides(httpClientInterceptor, contextId: contextId); - uriParser ??= UriParser(); + uriParser ??= UriParser(); - if (devToolsService != null) { - devToolsService!.init(this); - } + if (devToolsService != null) { + devToolsService!.init(this); + } - if (autoExecuteEntrypoint) { - executeEntrypoint(); - } + controlledInitCompleter.complete(); + + if (autoExecuteEntrypoint) { + executeEntrypoint(); + } + }); } late WebFViewController _view; @@ -924,7 +944,7 @@ class WebFController { HistoryModule get history => _module.moduleManager.getModule('History')!; - static Uri fallbackBundleUri([int? id]) { + static Uri fallbackBundleUri([double? id]) { // The fallback origin uri, like `vm://bundle/0` return Uri(scheme: 'vm', host: 'bundle', path: id != null ? '$id' : null); } @@ -940,13 +960,13 @@ class WebFController { // Wait for next microtask to make sure C++ native Elements are GC collected. Completer completer = Completer(); - Future.microtask(() { + Future.microtask(() async { _module.dispose(); - _view.dispose(); + await _view.dispose(); // RenderViewportBox will not disposed when reload, just remove all children and clean all resources. _view.viewport.reload(); - int oldId = _view.contextId; + double oldId = _view.contextId; _view = WebFViewController(view.viewportWidth, view.viewportHeight, background: _view.background, @@ -954,8 +974,11 @@ class WebFController { rootController: this, navigationDelegate: _view.navigationDelegate, gestureListener: _view.gestureListener, + runningThread: runningThread, originalViewport: _view.viewport); + await _view.initialize(); + _module = WebFModuleController(this, _view.contextId); // Reconnect the new contextId to the Controller @@ -1065,9 +1088,9 @@ class WebFController { bool _disposed = false; bool get disposed => _disposed; - void dispose() { + Future dispose() async { _module.dispose(); - _view.dispose(); + await _view.dispose(); _controllerMap[_view.contextId] = null; _controllerMap.remove(_view.contextId); _nameIdMap.remove(name); @@ -1086,6 +1109,7 @@ class WebFController { Future executeEntrypoint( {bool shouldResolve = true, bool shouldEvaluate = true, AnimationController? animationController}) async { if (_entrypoint != null && shouldResolve) { + await controlledInitCompleter.future; await Future.wait([ _resolveEntrypoint(), _module.initialize() @@ -1140,7 +1164,7 @@ class WebFController { assert(!_view._disposed, 'WebF have already disposed'); if (_entrypoint != null) { WebFBundle entrypoint = _entrypoint!; - int contextId = _view.contextId; + double contextId = _view.contextId; assert(entrypoint.isResolved, 'The webf bundle $entrypoint is not resolved to evaluate.'); // entry point start parse. diff --git a/webf/lib/src/module/module_manager.dart b/webf/lib/src/module/module_manager.dart index 3269661b95..8cd6f337db 100644 --- a/webf/lib/src/module/module_manager.dart +++ b/webf/lib/src/module/module_manager.dart @@ -56,7 +56,7 @@ void _defineModule(ModuleCreator moduleCreator) { } class ModuleManager { - final int contextId; + final double contextId; final WebFController controller; final Map _moduleMap = {}; bool disposed = false; diff --git a/webf/lib/src/module/schedule_frame.dart b/webf/lib/src/module/schedule_frame.dart index ac3d155f7b..a8855cd564 100644 --- a/webf/lib/src/module/schedule_frame.dart +++ b/webf/lib/src/module/schedule_frame.dart @@ -9,21 +9,18 @@ typedef DoubleCallback = void Function(double); typedef VoidCallback = void Function(); mixin ScheduleFrameMixin { - int _id = 1; final Map _animationFrameCallbackMap = {}; - int requestAnimationFrame(DoubleCallback callback) { - int id = _id++; - _animationFrameCallbackMap[id] = true; + void requestAnimationFrame(int newFrameId, DoubleCallback callback) { + _animationFrameCallbackMap[newFrameId] = true; SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { - if (_animationFrameCallbackMap.containsKey(id)) { - _animationFrameCallbackMap.remove(id); + if (_animationFrameCallbackMap.containsKey(newFrameId)) { + _animationFrameCallbackMap.remove(newFrameId); double highResTimeStamp = timeStamp.inMicroseconds / 1000; callback(highResTimeStamp); } }); SchedulerBinding.instance.scheduleFrame(); - return id; } void cancelAnimationFrame(int id) { diff --git a/webf/lib/src/module/timer.dart b/webf/lib/src/module/timer.dart index 62361e807e..d955bca349 100644 --- a/webf/lib/src/module/timer.dart +++ b/webf/lib/src/module/timer.dart @@ -57,17 +57,14 @@ class PausablePeriodicTimer implements Timer { } mixin TimerMixin { - int _timerId = 1; final Map _timerMap = {}; - int setTimeout(int timeout, void Function() callback) { + void setTimeout(int newTimerId, int timeout, void Function() callback) { Duration timeoutDurationMS = Duration(milliseconds: timeout); - int id = _timerId++; - _timerMap[id] = Timer(timeoutDurationMS, () { + _timerMap[newTimerId] = Timer(timeoutDurationMS, () { callback(); - _timerMap.remove(id); + _timerMap.remove(newTimerId); }); - return id; } void clearTimeout(int timerId) { @@ -78,13 +75,11 @@ mixin TimerMixin { } } - int setInterval(int timeout, void Function() callback) { + void setInterval(int newTimerId, int timeout, void Function() callback) { Duration timeoutDurationMS = Duration(milliseconds: timeout); - int id = _timerId++; - _timerMap[id] = PausablePeriodicTimer(timeoutDurationMS, (_) { + _timerMap[newTimerId] = PausablePeriodicTimer(timeoutDurationMS, (_) { callback(); }); - return id; } void pauseInterval() { diff --git a/webf/lib/src/painting/cached_network_image.dart b/webf/lib/src/painting/cached_network_image.dart index 8eb605d266..7354e30445 100644 --- a/webf/lib/src/painting/cached_network_image.dart +++ b/webf/lib/src/painting/cached_network_image.dart @@ -35,7 +35,7 @@ class CachedNetworkImage extends ImageProvider { final double scale; - final int? contextId; + final double? contextId; final Map? headers; diff --git a/webf/lib/src/painting/image_provider_factory.dart b/webf/lib/src/painting/image_provider_factory.dart index 0a6bbd26dd..6ad91ee2db 100644 --- a/webf/lib/src/painting/image_provider_factory.dart +++ b/webf/lib/src/painting/image_provider_factory.dart @@ -22,7 +22,7 @@ class ImageProviderParams { } class CachedNetworkImageProviderParams extends ImageProviderParams { - int? contextId; + double? contextId; CachedNetworkImageProviderParams(this.contextId, {int? cachedWidth, int? cachedHeight, BoxFit objectFit = BoxFit.fill}) @@ -120,7 +120,7 @@ ImageType parseImageUrl(Uri resolvedUri, {String cache = 'auto'}) { } ImageProvider? getImageProvider(Uri resolvedUri, - {int? contextId, cache = 'auto', BoxFit objectFit = BoxFit.fill, int? cachedWidth, int? cachedHeight}) { + {double? contextId, cache = 'auto', BoxFit objectFit = BoxFit.fill, int? cachedWidth, int? cachedHeight}) { ImageType imageType = parseImageUrl(resolvedUri, cache: cache); ImageProviderFactory factory = _getImageProviderFactory(imageType); diff --git a/webf/lib/src/rendering/box_model.dart b/webf/lib/src/rendering/box_model.dart index dd259f582f..c667a644a7 100644 --- a/webf/lib/src/rendering/box_model.dart +++ b/webf/lib/src/rendering/box_model.dart @@ -45,8 +45,8 @@ Offset getLayoutTransformTo(RenderObject current, RenderObject ancestor, {bool e } renderers.add(ancestor); Offset offset = Offset.zero; - final Matrix4 transform = Matrix4.identity(); + for (int index = renderers.length - 1; index > 0; index -= 1) { RenderObject parentRenderer = renderers[index]; RenderObject childRenderer = renderers[index - 1]; @@ -570,6 +570,11 @@ class RenderLayoutBox extends RenderBoxModel Matrix4? transform = (childRenderStyle as CSSRenderStyle).transformMatrix; double maxScrollableX = childRenderStyle.left.computedValue + childScrollableSize!.width; + + // maxScrollableX could be infinite due to the percentage value which depends on the parent box size, + // but in this stage, the parent's size will always to zero during the first initial layout. + if (maxScrollableX.isInfinite) return; + if (transform != null) { maxScrollableX += transform.getTranslation()[0]; } @@ -589,6 +594,11 @@ class RenderLayoutBox extends RenderBoxModel } double maxScrollableY = childRenderStyle.top.computedValue + childScrollableSize.height; + + // maxScrollableX could be infinite due to the percentage value which depends on the parent box size, + // but in this stage, the parent's size will always to zero during the first initial layout. + if (maxScrollableY.isInfinite) return; + if (transform != null) { maxScrollableY += transform.getTranslation()[1]; } diff --git a/webf/lib/src/widget/webf.dart b/webf/lib/src/widget/webf.dart index 1a615c8eeb..472d52ac68 100644 --- a/webf/lib/src/widget/webf.dart +++ b/webf/lib/src/widget/webf.dart @@ -17,42 +17,68 @@ import 'package:webf/css.dart'; typedef OnControllerCreated = void Function(WebFController controller); class WebF extends StatefulWidget { - // The background color for viewport, default to transparent. + /// The background color for viewport, default to transparent. final Color? background; - // the width of webFWidget + /// the width of webFWidget final double? viewportWidth; - // the height of webFWidget + /// the height of webFWidget final double? viewportHeight; - // The initial bundle to load. + /// The initial bundle to load. final WebFBundle? bundle; - // The animationController of Flutter Route object. - // Pass this object to webFWidget to make sure webF execute JavaScripts scripts after route transition animation completed. + /// The animationController of Flutter Route object. + /// Pass this object to webFWidget to make sure webF execute JavaScripts scripts after route transition animation completed. final AnimationController? animationController; - // The methods of the webFNavigateDelegation help you implement custom behaviors that are triggered - // during a webf view's process of loading, and completing a navigation request. + /// The methods of the webFNavigateDelegation help you implement custom behaviors that are triggered + /// during a webf view's process of loading, and completing a navigation request. final WebFNavigationDelegate? navigationDelegate; - // A method channel for receiving messaged from JavaScript code and sending message to JavaScript. + /// A method channel for receiving messaged from JavaScript code and sending message to JavaScript. final WebFMethodChannel? javaScriptChannel; - // Register the RouteObserver to observer page navigation. - // This is useful if you wants to pause webf timers and callbacks when webf widget are hidden by page route. - // https://api.flutter.dev/flutter/widgets/RouteObserver-class.html + /// Register the RouteObserver to observer page navigation. + /// This is useful if you wants to pause webf timers and callbacks when webf widget are hidden by page route. + /// https://api.flutter.dev/flutter/widgets/RouteObserver-class.html final RouteObserver>? routeObserver; - // Trigger when webf controller once created. + /// Trigger when webf controller once created. final OnControllerCreated? onControllerCreated; + /// Specify the running thread for your JavaScript codes. + /// Default value: DedicatedThread(); + /// + /// [DedicatedThread] : Executes your JavaScript code in a dedicated thread. + /// Advantage: Ideal for developers building applications with hundreds of DOM elements in JavaScript, + /// where common user interactions like scrolling and swiping do not heavily depend on the JavaScript. + /// Disadvantages: Increase communicate overhead since the JavaScript is runs in a separate thread. + /// Data exchanges between Dart and JavaScript requires mutex and synchronization. + /// + /// [DedicatedThreadGroup] : Executes multiple JavaScript contexts in a single thread. + /// Rather than creating a new thread for each WebF instance, this option allows placing multiple WebF instances and their JavaScript contexts + /// into one dedicated thread. + /// Advantage: JavaScript contexts in the same group can share global class and string data, reducing initialization time + /// for new WebF instances and their JavaScript contexts in this thread. + /// Disadvantages: Since all group members run in the same thread, one can block the others, even if they are not strong related. + /// + /// [FlutterUIThread] : Executes your JavaScript code within the Flutter UI thread. + /// Advantage: This is the best mode for minimizing communication time between Dart and JavaScript, especially when you have animations + /// controlled by JavaScript and rendered by Flutter. If you're building animations influenced by user interactions, like figure gestures, + /// setting the runningThread to [FlutterUIThread] is the optimal choice. + /// Disadvantages: Any executing of JavaScript will block the executing of Dart codes. + /// If a JavaScript function takes longer than a single frame, it could cause lag, as all Dart code executing will be blocked by your JavaScript. + /// Be mindful of JavaScript executing times when using this mode. + /// + final WebFThread? runningThread; + final LoadErrorHandler? onLoadError; final LoadHandler? onLoad; - // https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event + /// https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event final LoadHandler? onDOMContentLoaded; final JSErrorHandler? onJSError; @@ -131,6 +157,7 @@ class WebF extends StatefulWidget { // webf's http client interceptor. this.httpClientInterceptor, this.uriParser, + WebFThread? runningThread, this.routeObserver, this.initialCookies, this.preloadedBundles, @@ -149,7 +176,8 @@ class WebF extends StatefulWidget { this.animationController, this.onJSError, this.resizeToAvoidBottomInsets = true}) - : super(key: key); + : runningThread = runningThread ?? DedicatedThread(), + super(key: key); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -352,6 +380,7 @@ class WebFRootRenderObjectWidget extends MultiChildRenderObjectWidget { onDOMContentLoaded: _webfWidget.onDOMContentLoaded, onLoadError: _webfWidget.onLoadError, onJSError: _webfWidget.onJSError, + runningThread: _webfWidget.runningThread, methodChannel: _webfWidget.javaScriptChannel, gestureListener: _webfWidget.gestureListener, navigationDelegate: _webfWidget.navigationDelegate, @@ -369,7 +398,9 @@ class WebFRootRenderObjectWidget extends MultiChildRenderObjectWidget { OnControllerCreated? onControllerCreated = _webfWidget.onControllerCreated; if (onControllerCreated != null) { - onControllerCreated(controller); + controller.controlledInitCompleter.future.then((_) { + onControllerCreated(controller); + }); } return controller.view.getRootRenderObject(); diff --git a/webf/test/src/foundation/http_cache.dart b/webf/test/src/foundation/http_cache.dart index c824fd810e..876493987e 100644 --- a/webf/test/src/foundation/http_cache.dart +++ b/webf/test/src/foundation/http_cache.dart @@ -14,7 +14,7 @@ import '../../local_http_server.dart'; void main() { var server = LocalHttpServer.getInstance(); - int contextId = 1; + double contextId = 1; HttpOverrides.global = null; setupHttpOverrides(null, contextId: contextId); HttpClient httpClient = HttpClient(); diff --git a/webf/test/src/foundation/http_client.dart b/webf/test/src/foundation/http_client.dart index defcaa44d4..7b75aa6d68 100644 --- a/webf/test/src/foundation/http_client.dart +++ b/webf/test/src/foundation/http_client.dart @@ -61,7 +61,7 @@ void main() { group('HttpRequest', () { var server = LocalHttpServer.getInstance(); - int contextId = 3; + double contextId = 3; HttpOverrides.global = null; setupHttpOverrides(null, contextId: contextId); HttpClient httpClient = HttpClient(); diff --git a/webf/test/src/foundation/http_client_interceptor.dart b/webf/test/src/foundation/http_client_interceptor.dart index b7140d6580..2c69c9e6d3 100644 --- a/webf/test/src/foundation/http_client_interceptor.dart +++ b/webf/test/src/foundation/http_client_interceptor.dart @@ -11,7 +11,7 @@ import 'package:webf/foundation.dart'; import '../../local_http_server.dart'; -const int contextId = 2; +const double contextId = 2; void main() { var server = LocalHttpServer.getInstance(); group('HttpClientInterceptor', () { diff --git a/website/docs/tutorials/guides-for-flutter-developer/flutter_widget_element.md b/website/docs/tutorials/guides-for-flutter-developer/flutter_widget_element.md index eacbd129df..f5de8eab2d 100644 --- a/website/docs/tutorials/guides-for-flutter-developer/flutter_widget_element.md +++ b/website/docs/tutorials/guides-for-flutter-developer/flutter_widget_element.md @@ -74,7 +74,7 @@ built with WebF. Here's a step-by-step guide: ); } } - ``` + ``` Interestingly, the WidgetElement shares many similarities with the State associated with StatefulWidget in Flutter. So, if you're already acquainted with typical Flutter app development, working with WidgetElement should come naturally.