diff --git a/.github/workflows/example_build.yml b/.github/workflows/example_build.yml index 625de0835a..8c1e6455e3 100644 --- a/.github/workflows/example_build.yml +++ b/.github/workflows/example_build.yml @@ -10,7 +10,7 @@ on: env: nodeVersion: "16" cmakeVersion: "3.22.x" - flutterVersion: "3.0.4" + flutterVersion: "3.0.5" jobs: build_android-app_in_macos: diff --git a/.github/workflows/integration_test_flutter.yml b/.github/workflows/integration_test_flutter.yml index cb749eef5f..0cf2649772 100644 --- a/.github/workflows/integration_test_flutter.yml +++ b/.github/workflows/integration_test_flutter.yml @@ -5,7 +5,7 @@ on: [workflow_dispatch, pull_request] env: nodeVersion: "16" cmakeVersion: "3.22.x" - flutterVersion: "3.0.4" + flutterVersion: "3.0.5" # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -30,7 +30,7 @@ jobs: with: cmake-version: ${{ env.cmakeVersion }} - run: npm i - - run: ENABLE_ASAN=true npm run build:bridge:macos + - run: npm run build:bridge:macos - uses: actions/upload-artifact@v2 with: name: macos_bridge_binary @@ -49,22 +49,6 @@ jobs: - run: npm i - run: ENABLE_ASAN=true npm run build:bridge:linux - run: node scripts/run_bridge_unit_test.js - id: bridge_test - - name: Check on failures - if: steps.bridge_test.outcome == 'success' - run: exit 0 - - run: sudo chmod -R +rwx /cores/* # Enable access to core dumps - - run: ulimit -c # should output 0 if disabled - - run: ulimit -c unlimited - - run: ulimit -c # should output 'unlimited' now - - run: sudo touch /cores/test - - run: ls /cores - - run: sudo rm /cores/test - - uses: actions/upload-artifact@master # capture all crashes as build artifacts - if: steps.bridge_test.outcome != 'success' - with: - name: crashes - path: /cores webf_unit_test: runs-on: ubuntu-latest diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 31ac8cd9d3..1f6fd57d37 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -59,10 +59,15 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") endif() if (ENABLE_ASAN) - add_compile_options(-fsanitize=address -fno-omit-frame-pointer -O1) + add_compile_options(-fsanitize=address -fno-omit-frame-pointer) add_link_options(-fsanitize=address -fno-omit-frame-pointer) endif () +if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") + # Avoid quickjs stackoverflow. + add_compile_options(-O1) +endif() + if (DEFINED PLATFORM) if (${PLATFORM} STREQUAL "OS") add_compile_options(-fno-aligned-allocation) @@ -194,6 +199,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/executing_context.cc core/script_state.cc core/page.cc + core/dart_methods.cc core/executing_context_data.cc core/fileapi/blob.cc core/fileapi/blob_part.cc @@ -270,6 +276,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/html/html_anchor_element.cc core/html/html_image_element.cc core/html/html_script_element.cc + core/html/html_link_element.cc core/html/html_unknown_element.cc core/html/image.cc core/html/canvas/html_canvas_element.cc @@ -362,6 +369,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") out/qjs_html_html_element.cc out/qjs_html_image_element.cc out/qjs_html_canvas_element.cc + out/qjs_html_link_element.cc out/qjs_image.cc out/qjs_widget_element.cc out/qjs_canvas_rendering_context_2d.cc @@ -386,6 +394,8 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") out/html_element_factory.cc out/html_names.cc out/script_type_names.cc + out/defined_properties.cc + out/defined_properties_initializer.cc out/element_attribute_names.cc ) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index c820d6d187..ffa9d6de20 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -37,6 +37,7 @@ #include "qjs_html_html_element.h" #include "qjs_html_image_element.h" #include "qjs_html_input_element.h" +#include "qjs_html_link_element.h" #include "qjs_html_script_element.h" #include "qjs_html_template_element.h" #include "qjs_html_textarea_element.h" @@ -119,6 +120,7 @@ void InstallBindings(ExecutingContext* context) { QJSHTMLButtonElement::Install(context); QJSImage::Install(context); QJSHTMLScriptElement::Install(context); + QJSHTMLLinkElement::Install(context); QJSHTMLUnknownElement::Install(context); QJSHTMLTemplateElement::Install(context); QJSHTMLCanvasElement::Install(context); diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.cc b/bridge/bindings/qjs/cppgc/mutation_scope.cc index 0e6678df88..5be04f98ff 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.cc +++ b/bridge/bindings/qjs/cppgc/mutation_scope.cc @@ -4,6 +4,7 @@ */ #include "mutation_scope.h" +#include "bindings/qjs/script_wrappable.h" #include "core/executing_context.h" namespace webf { diff --git a/bridge/bindings/qjs/js_based_event_listener.cc b/bridge/bindings/qjs/js_based_event_listener.cc index 8d40b5f97b..0f9b82237e 100644 --- a/bridge/bindings/qjs/js_based_event_listener.cc +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -14,7 +14,7 @@ void JSBasedEventListener::Invoke(ExecutingContext* context, Event* event, Excep assert(context); assert(event); - if (!context->IsValid()) + if (!context->IsContextValid()) return; // Step 10: Call a listener with event's currentTarget as receiver and event // and handle errors if thrown. diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index a2d7128172..a5965e6a9e 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -9,6 +9,7 @@ #include #include "core/dom/events/event_listener.h" #include "core/executing_context.h" +#include "foundation/casting.h" namespace webf { diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index 37b438bfa8..5e3b3b5bfe 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -4,6 +4,7 @@ */ #include "rejected_promises.h" +#include "bindings/qjs/cppgc/mutation_scope.h" #include "core/executing_context.h" namespace webf { diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 2289bcbc17..95b01c2f41 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -60,7 +60,7 @@ class ScriptPromiseResolver { } void ResolveOrReject(JSValue value, ResolutionState new_state) { - if (state_ != kPending || !context_->IsValid() || !context_) + if (state_ != kPending || !context_->IsContextValid() || !context_) return; assert(new_state == kResolving || new_state == kRejecting); state_ = new_state; diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 9e974baa30..38873173aa 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -147,7 +147,7 @@ AtomicString ScriptValue::ToString() const { return {ctx_, value_}; } -NativeValue ScriptValue::ToNative() const { +NativeValue ScriptValue::ToNative(ExceptionState& exception_state) const { int8_t tag = JS_VALUE_GET_TAG(value_); switch (tag) { @@ -175,7 +175,7 @@ NativeValue ScriptValue::ToNative() const { Converter>::FromValue(ctx_, value_, ASSERT_NO_EXCEPTION()); auto* result = new NativeValue[values.size()]; for (int i = 0; i < values.size(); i++) { - result[i] = values[i].ToNative(); + result[i] = values[i].ToNative(exception_state); } return Native_NewList(values.size(), result); } else if (JS_IsObject(value_)) { @@ -183,7 +183,7 @@ NativeValue ScriptValue::ToNative() const { auto* event_target = toScriptWrappable(value_); return Native_NewPtr(JSPointerType::Others, event_target->bindingObject()); } - return NativeValueConverter::ToNativeValue(*this); + return NativeValueConverter::ToNativeValue(*this, exception_state); } } default: diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 50511419cf..5ebd8397a9 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -60,7 +60,7 @@ class ScriptValue final { // Create a new ScriptValue from call JSON.stringify to current value. ScriptValue ToJSONStringify(ExceptionState* exception) const; AtomicString ToString() const; - NativeValue ToNative() const; + NativeValue ToNative(ExceptionState& exception_state) const; bool IsException() const; bool IsEmpty() const; diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 0312b3642d..d624314938 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -75,6 +75,41 @@ static int HandleJSPropertySetterCallback(JSContext* ctx, auto* object = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); auto* wrapper_type_info = object->GetWrapperTypeInfo(); + ExecutingContext* context = ExecutingContext::From(ctx); + JSValue prototypeObject = context->contextData()->prototypeForType(wrapper_type_info); + if (JS_HasProperty(ctx, prototypeObject, atom)) { + JSValue target = JS_DupValue(ctx, prototypeObject); + JSValue setterFunc = JS_UNDEFINED; + while (JS_IsUndefined(setterFunc)) { + JSPropertyDescriptor descriptor; + descriptor.setter = JS_UNDEFINED; + descriptor.getter = JS_UNDEFINED; + descriptor.value = JS_UNDEFINED; + JS_GetOwnProperty(ctx, &descriptor, target, atom); + setterFunc = descriptor.setter; + if (JS_IsFunction(ctx, setterFunc)) { + JS_FreeValue(ctx, descriptor.getter); + break; + } + + JSValue new_target = JS_GetPrototype(ctx, target); + JS_FreeValue(ctx, target); + target = new_target; + JS_FreeValue(ctx, descriptor.getter); + JS_FreeValue(ctx, descriptor.setter); + } + + assert_m(JS_IsFunction(ctx, setterFunc), "Setter on prototype should be an function."); + JSValue ret = JS_Call(ctx, setterFunc, obj, 1, &value); + if (JS_IsException(ret)) + return -1; + + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, setterFunc); + JS_FreeValue(ctx, target); + return 0; + } + if (wrapper_type_info->indexed_property_setter_handler_ != nullptr && JS_AtomIsTaggedInt(atom)) { return wrapper_type_info->indexed_property_setter_handler_(ctx, obj, JS_AtomToUInt32(atom), value); } else if (wrapper_type_info->string_property_setter_handler_ != nullptr) { @@ -165,6 +200,8 @@ void ScriptWrappable::InitializeQuickJSObject() { if (!JS_IsNull(return_value)) { desc->flags = JS_PROP_ENUMERABLE; desc->value = return_value; + desc->getter = JS_NULL; + desc->setter = JS_NULL; return true; } } @@ -175,6 +212,8 @@ void ScriptWrappable::InitializeQuickJSObject() { if (!JS_IsNull(return_value)) { desc->flags = JS_PROP_ENUMERABLE; desc->value = return_value; + desc->getter = JS_NULL; + desc->setter = JS_NULL; return true; } } diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index fd7a3e377c..f4462d8e2e 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -8,6 +8,7 @@ #include #include "bindings/qjs/cppgc/garbage_collected.h" +#include "core/executing_context.h" #include "foundation/macros.h" #include "wrapper_type_info.h" diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 2b5d4c3b22..2b36128206 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -65,6 +65,7 @@ enum { JS_CLASS_HTML_IMAGE_ELEMENT, JS_CLASS_HTML_SCRIPT_ELEMENT, JS_CLASS_HTML_ANCHOR_ELEMENT, + JS_CLASS_HTML_LINK_ELEMENT, JS_CLASS_HTML_CANVAS_ELEMENT, JS_CLASS_IMAGE, JS_CLASS_CANVAS_RENDERING_CONTEXT, diff --git a/bridge/core/binding_call_methods.json5 b/bridge/core/binding_call_methods.json5 index 7adf4bad11..bdf23811ea 100644 --- a/bridge/core/binding_call_methods.json5 +++ b/bridge/core/binding_call_methods.json5 @@ -150,6 +150,8 @@ "getElementsByName", "getElementsByTagName", "id", - "className" + "className", + "cookie", + "class" ] } diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 3ef2220ce5..b5d1da1ae1 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -25,7 +25,11 @@ void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_ob BindingObject::BindingObject(ExecutingContext* context) : context_(context) {} BindingObject::~BindingObject() { - delete binding_object_; + // Set below properties to nullptr to avoid dart callback to native. + binding_object_->disposed_ = true; + binding_object_->binding_target_ = nullptr; + binding_object_->invoke_binding_methods_from_dart = nullptr; + binding_object_->invoke_bindings_methods_from_native = nullptr; } BindingObject::BindingObject(ExecutingContext* context, NativeBindingObject* native_binding_object) @@ -101,20 +105,24 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, std::vector arguments; arguments.reserve(argc + 1); - arguments.emplace_back(NativeValueConverter::ToNativeValue(id)); + + ExceptionState exception_state; + for (int i = 0; i < argc; i++) { - arguments.emplace_back(argv[i].ToNative()); + arguments.emplace_back(argv[i].ToNative(exception_state)); + } + + if (exception_state.HasException()) { + event_target->GetExecutingContext()->HandleException(exception_state); + return ScriptValue::Empty(ctx); } - ExceptionState exception_state; NativeValue result = event_target->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, arguments.size(), arguments.data(), exception_state); if (exception_state.HasException()) { - JSValue error = JS_GetException(ctx); - event_target->GetExecutingContext()->ReportError(error); - JS_FreeValue(ctx, error); + event_target->GetExecutingContext()->HandleException(exception_state); return ScriptValue::Empty(ctx); } return ScriptValue(ctx, result); @@ -125,7 +133,7 @@ void BindingObject::HandleAnonymousAsyncCalledFromDart(void* ptr, int32_t contextId, const char* errmsg) { auto* promise_context = static_cast(ptr); - if (!promise_context->context->IsValid()) + if (!promise_context->context->IsContextValid()) return; if (promise_context->context->contextId() != contextId) return; @@ -173,13 +181,20 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, arguments.emplace_back(NativeValueConverter>::ToNativeValue( reinterpret_cast(HandleAnonymousAsyncCalledFromDart))); + ExceptionState exception_state; + for (int i = 0; i < argc; i++) { - arguments.emplace_back(argv[i].ToNative()); + arguments.emplace_back(argv[i].ToNative(exception_state)); } - ExceptionState exception_state; event_target->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, arguments.data(), exception_state); + + if (exception_state.HasException()) { + event_target->GetExecutingContext()->HandleException(exception_state); + return ScriptValue::Empty(ctx); + } + return promise_resolver->Promise().ToValue(); } diff --git a/bridge/core/binding_object.h b/bridge/core/binding_object.h index 2e7449c253..a32f2f7f02 100644 --- a/bridge/core/binding_object.h +++ b/bridge/core/binding_object.h @@ -43,6 +43,7 @@ struct NativeBindingObject : public DartReadable { int32_t argc, NativeValue* argv); + bool disposed_{false}; BindingObject* binding_target_{nullptr}; InvokeBindingMethodsFromDart invoke_binding_methods_from_dart{nullptr}; InvokeBindingsMethodsFromNative invoke_bindings_methods_from_native{nullptr}; diff --git a/bridge/core/dart_methods.cc b/bridge/core/dart_methods.cc new file mode 100644 index 0000000000..ce348d5dcb --- /dev/null +++ b/bridge/core/dart_methods.cc @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "dart_methods.h" +#include + +namespace webf { + +webf::DartMethodPointer::DartMethodPointer(const uint64_t* dart_methods, int32_t dart_methods_length) { + 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++]); + +#if ENABLE_PROFILE + dartMethodPointer->getPerformanceEntries = reinterpret_cast(dart_methods[i++]); +#else + i++; +#endif + + onJsError = reinterpret_cast(dart_methods[i++]); + onJsLog = reinterpret_cast(dart_methods[i++]); + + assert_m(i == dart_methods_length, "Dart native methods count is not equal with C++ side method registrations."); +} +} // namespace webf \ No newline at end of file diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index 4a4bf568ca..8cbe605f2c 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -14,21 +14,26 @@ #include "webf_bridge.h" #include "foundation/native_string.h" +#include "foundation/native_value.h" + #define WEBF_EXPORT __attribute__((__visibility__("default"))) namespace webf { using AsyncCallback = void (*)(void* callbackContext, int32_t contextId, const char* errmsg); using AsyncRAFCallback = void (*)(void* callbackContext, int32_t contextId, double result, const char* errmsg); -using AsyncModuleCallback = void (*)(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json); +using AsyncModuleCallback = NativeValue* (*)(void* callbackContext, + int32_t contextId, + const char* errmsg, + NativeValue* value); using AsyncBlobCallback = void (*)(void* callbackContext, int32_t contextId, const char* error, uint8_t* bytes, int32_t length); -typedef NativeString* (*InvokeModule)(void* callbackContext, - int32_t contextId, - NativeString* moduleName, - NativeString* method, - NativeString* params, - AsyncModuleCallback callback); +typedef NativeValue* (*InvokeModule)(void* callbackContext, + int32_t contextId, + NativeString* moduleName, + NativeString* method, + NativeValue* params, + AsyncModuleCallback callback); typedef void (*RequestBatchUpdate)(int32_t contextId); typedef void (*ReloadApp)(int32_t contextId); typedef int32_t (*SetTimeout)(void* callbackContext, int32_t contextId, AsyncCallback callback, int32_t timeout); @@ -72,7 +77,9 @@ using SimulatePointer = void (*)(MousePointer*, int32_t length, int32_t pointer) using SimulateInputText = void (*)(NativeString* nativeString); struct DartMethodPointer { - DartMethodPointer() = default; + DartMethodPointer() = delete; + explicit DartMethodPointer(const uint64_t* dart_methods, int32_t dartMethodsLength); + InvokeModule invokeModule{nullptr}; RequestBatchUpdate requestBatchUpdate{nullptr}; ReloadApp reloadApp{nullptr}; diff --git a/bridge/core/defined_properties.json5 b/bridge/core/defined_properties.json5 new file mode 100644 index 0000000000..de4c0d5e70 --- /dev/null +++ b/bridge/core/defined_properties.json5 @@ -0,0 +1,13 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "defined_properties" + } + ] + }, + "data": [ + // The data of defined_properties.json5 will be injected by code_generator scripts. + ] +} diff --git a/bridge/core/defined_properties_initializer.json5 b/bridge/core/defined_properties_initializer.json5 new file mode 100644 index 0000000000..47e37c0d5a --- /dev/null +++ b/bridge/core/defined_properties_initializer.json5 @@ -0,0 +1,13 @@ +{ + "metadata": { + "templates": [ + { + "template": "defined_properties_initializer", + "filename": "defined_properties_initializer" + } + ] + }, + "data": [ + // The data of defined_properties_initializer.json5 will be injected by code_generator scripts. + ] +} diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc index 4b134e0f45..88dc9483f7 100644 --- a/bridge/core/dom/character_data.cc +++ b/bridge/core/dom/character_data.cc @@ -6,6 +6,7 @@ #include "character_data.h" #include "built_in_string.h" #include "core/dom/document.h" +#include "qjs_character_data.h" namespace webf { @@ -31,6 +32,10 @@ void CharacterData::setNodeValue(const AtomicString& value, ExceptionState& exce setData(!value.IsEmpty() ? value : built_in_string::kempty_string, exception_state); } +bool CharacterData::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSCharacterData::IsAttributeDefinedInternal(key) || Node::IsAttributeDefinedInternal(key); +} + CharacterData::CharacterData(TreeScope& tree_scope, const AtomicString& text, Node::ConstructionType type) : Node(tree_scope.GetDocument().GetExecutingContext(), &tree_scope, type), data_(text) { assert(type == kCreateOther || type == kCreateText); diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index d6ae0ef3f2..b7e99ebc69 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -24,6 +24,8 @@ class CharacterData : public Node { bool IsCharacterDataNode() const override; void setNodeValue(const AtomicString&, ExceptionState&) override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + protected: CharacterData(TreeScope& tree_scope, const AtomicString& text, ConstructionType type); diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index cf345b173c..f5d67e1905 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -23,6 +23,7 @@ #include "foundation/ascii_types.h" #include "foundation/native_value_converter.h" #include "html_element_factory.h" +#include "qjs_document.h" namespace webf { @@ -310,6 +311,10 @@ std::shared_ptr Document::GetWindowAttributeEventListener(const A return window->GetAttributeEventListener(event_type); } +bool Document::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSDocument::IsAttributeDefinedInternal(key) || Node::IsAttributeDefinedInternal(key); +} + void Document::Trace(GCVisitor* visitor) const { script_animation_controller_.Trace(visitor); ContainerNode::Trace(visitor); diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts index 133a553f3a..0a562d97db 100644 --- a/bridge/core/dom/document.d.ts +++ b/bridge/core/dom/document.d.ts @@ -12,6 +12,7 @@ import {HTMLAllCollection} from "../html/html_all_collection"; interface Document extends Node { readonly all: HTMLAllCollection; body: HTMLBodyElement | null; + cookie: DartImpl; readonly head: HTMLHeadElement | null; readonly documentElement: HTMLHtmlElement | null; // Legacy impl: get the polyfill implements from global object. diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index a2270a59e1..217a53b302 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -99,6 +99,8 @@ class Document : public ContainerNode, public TreeScope { ExceptionState& exception_state); std::shared_ptr GetWindowAttributeEventListener(const AtomicString& event_type); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + void Trace(GCVisitor* visitor) const override; private: diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 450dd55199..bfba99b8f7 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -15,6 +15,7 @@ #include "element_attribute_names.h" #include "foundation/native_value_converter.h" #include "html_element_type_helper.h" +#include "qjs_element.h" #include "text.h" namespace webf { @@ -37,7 +38,7 @@ bool Element::hasAttribute(const AtomicString& name, ExceptionState& exception_s } AtomicString Element::getAttribute(const AtomicString& name, ExceptionState& exception_state) { - return EnsureElementAttributes().GetAttribute(name); + return EnsureElementAttributes().getAttribute(name, exception_state); } void Element::setAttribute(const AtomicString& name, const AtomicString& value) { @@ -47,7 +48,7 @@ void Element::setAttribute(const AtomicString& name, const AtomicString& value) void Element::setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state) { if (EnsureElementAttributes().hasAttribute(name, exception_state)) { - AtomicString&& oldAttribute = EnsureElementAttributes().GetAttribute(name); + AtomicString&& oldAttribute = EnsureElementAttributes().getAttribute(name, exception_state); if (!EnsureElementAttributes().setAttribute(name, value, exception_state)) { return; }; @@ -220,6 +221,10 @@ bool Element::IsWidgetElement() const { return false; } +bool Element::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSElement::IsAttributeDefinedInternal(key) || Node::IsAttributeDefinedInternal(key); +} + void Element::Trace(GCVisitor* visitor) const { visitor->Trace(attributes_); visitor->Trace(cssom_wrapper_); diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index 6432383501..f694432919 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -8,6 +8,8 @@ import {ParentNode} from "./parent_node"; interface Element extends Node, ParentNode { id: DartImpl; className: DartImpl; + class: DartImpl; + name: DartImpl; readonly attributes: ElementAttributes; readonly style: CSSStyleDeclaration; readonly clientHeight: DartImpl; diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 38e5e436bb..d34dbb8b6b 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -80,6 +80,7 @@ class Element : public ContainerNode { virtual void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) {} virtual bool IsWidgetElement() const; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; void Trace(GCVisitor* visitor) const override; protected: diff --git a/bridge/core/dom/element_data.h b/bridge/core/dom/element_data.h index 326fd93f41..3787289a41 100644 --- a/bridge/core/dom/element_data.h +++ b/bridge/core/dom/element_data.h @@ -15,6 +15,7 @@ class ElementData { void CopyWith(ElementData* other); private: + AtomicString class_; }; } // namespace webf diff --git a/bridge/core/dom/events/custom_event.cc b/bridge/core/dom/events/custom_event.cc index c1608f6263..649a2185fc 100644 --- a/bridge/core/dom/events/custom_event.cc +++ b/bridge/core/dom/events/custom_event.cc @@ -29,7 +29,7 @@ CustomEvent::CustomEvent(ExecutingContext* context, const AtomicString& type, Ex CustomEvent::CustomEvent(ExecutingContext* context, const AtomicString& type, NativeCustomEvent* native_custom_event) : Event(context, type, &native_custom_event->native_event), - detail_(ScriptValue::CreateJsonObject(ctx(), native_custom_event->detail, strlen(native_custom_event->detail))) {} + detail_(ScriptValue(ctx(), *native_custom_event->detail)) {} CustomEvent::CustomEvent(ExecutingContext* context, const AtomicString& type, diff --git a/bridge/core/dom/events/custom_event.h b/bridge/core/dom/events/custom_event.h index b1bb307291..f83effbc5b 100644 --- a/bridge/core/dom/events/custom_event.h +++ b/bridge/core/dom/events/custom_event.h @@ -12,7 +12,7 @@ namespace webf { struct NativeCustomEvent { NativeEvent native_event; - const char* detail{nullptr}; + NativeValue* detail{nullptr}; }; class CustomEvent final : public Event { diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 24f0a63b0e..9b6dd166f2 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -55,10 +55,11 @@ struct NativeEvent { struct RawEvent : public DartReadable { uint64_t* bytes; int64_t length; + int8_t is_custom_event; }; template -T* toNativeEvent(RawEvent* raw_event) { +inline T* toNativeEvent(RawEvent* raw_event) { // NativeEvent members are memory aligned corresponding to NativeEvent. // So we can reinterpret_cast raw bytes pointer to NativeEvent type directly. return reinterpret_cast(raw_event->bytes); diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index 241470cdd9..572679a272 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -111,7 +111,7 @@ bool EventListenerMap::Remove(const AtomicString& event_type, return false; } -EventListenerVector* EventListenerMap::Find(const AtomicString& event_type) { +EventListenerVector* EventListenerMap::Find(const AtomicString& event_type) const { for (const auto& entry : entries_) { if (entry.first == event_type) return entry.second.get(); diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index 7532b974df..f7838b55fd 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -44,7 +44,7 @@ class EventListenerMap final { size_t* index_of_removed_listener, RegisteredEventListener* registered_event_listener, uint32_t* listener_count); - EventListenerVector* Find(const AtomicString& event_type); + EventListenerVector* Find(const AtomicString& event_type) const; void Trace(GCVisitor* visitor) const; diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 296f8748a4..f98f15ff4c 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -6,10 +6,10 @@ #include #include "binding_call_methods.h" #include "bindings/qjs/converter_impl.h" -#include "custom_event.h" #include "event_factory.h" #include "native_value_converter.h" #include "qjs_add_event_listener_options.h" +#include "qjs_event_target.h" #define PROPAGATION_STOPPED 1 #define PROPAGATION_CONTINUE 0 @@ -55,7 +55,8 @@ EventTarget::~EventTarget() { } #endif - GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, nullptr); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, + bindingObject()); } EventTarget::EventTarget(ExecutingContext* context) @@ -74,6 +75,9 @@ bool EventTarget::addEventListener(const AtomicString& event_type, const std::shared_ptr& event_listener, const std::shared_ptr& options, ExceptionState& exception_state) { + if (options == nullptr) { + return AddEventListenerInternal(event_type, event_listener, AddEventListenerOptions::Create()); + } return AddEventListenerInternal(event_type, event_listener, options); } @@ -192,6 +196,10 @@ bool EventTarget::IsEventTarget() const { return true; } +bool EventTarget::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSEventTarget::IsAttributeDefinedInternal(key); +} + void EventTarget::Trace(GCVisitor* visitor) const { ScriptWrappable::Trace(visitor); BindingObject::Trace(visitor); @@ -285,19 +293,11 @@ NativeValue EventTarget::HandleCallFromDartSide(const NativeValue* native_method } NativeValue EventTarget::HandleDispatchEventFromDart(int32_t argc, const NativeValue* argv) { - assert(argc == 3); + assert(argc == 2); AtomicString event_type = NativeValueConverter::FromNativeValue(ctx(), argv[0]); RawEvent* raw_event = NativeValueConverter>::FromNativeValue(argv[1]); - bool is_custom_event = NativeValueConverter::FromNativeValue(argv[2]); - - Event* event; - if (is_custom_event) { - event = MakeGarbageCollected(GetExecutingContext(), event_type, - toNativeEvent(raw_event)); - } else { - event = EventFactory::Create(GetExecutingContext(), event_type, raw_event); - } + Event* event = EventFactory::Create(GetExecutingContext(), event_type, raw_event); ExceptionState exception_state; event->SetTrusted(false); event->SetEventPhase(Event::kAtTarget); diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index e4dd34875f..4ff238265f 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -132,6 +132,9 @@ class EventTarget : public ScriptWrappable, public BindingObject { virtual bool IsNode() const { return false; } bool IsEventTarget() const override; + // Check the attribute is defined in native. + virtual bool IsAttributeDefinedInternal(const AtomicString& key) const; + void Trace(GCVisitor* visitor) const override; protected: diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index 8e59b9ad0e..852b3983f0 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -91,6 +91,24 @@ TEST(EventTarget, propertyEventHandler) { EXPECT_EQ(logCalled, true); } +TEST(EventTarget, overwritePropertyEventHandler) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let div = document.createElement('div'); " + "div.onclick = function() { return 1234; };" + "div.onclick = null;" + "console.log(div.onclick)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); +} + TEST(EventTarget, propertyEventOnWindow) { bool static errorCalled = false; bool static logCalled = false; diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index 3a77d92e86..2532b934dc 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -7,6 +7,7 @@ #include "bindings/qjs/exception_state.h" #include "built_in_string.h" #include "core/dom/element.h" +#include "foundation/native_value_converter.h" namespace webf { @@ -17,17 +18,26 @@ static inline bool IsNumberIndex(const StringView& name) { return f >= '0' && f <= '9'; } -ElementAttributes::ElementAttributes(Element* element) - : ScriptWrappable(element->ctx()), owner_event_target_id_(element->eventTargetId()) {} +ElementAttributes::ElementAttributes(Element* element) : ScriptWrappable(element->ctx()), element_(element) {} -AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { +AtomicString ElementAttributes::getAttribute(const AtomicString& name, ExceptionState& exception_state) { bool numberIndex = IsNumberIndex(name.ToStringView()); if (numberIndex) { return AtomicString::Empty(); } - return attributes_[name]; + AtomicString value = attributes_[name]; + + // Fallback to directly FFI access to dart. + if (value.IsEmpty()) { + NativeValue dart_result = element_->GetBindingProperty(name, exception_state); + if (dart_result.tag == NativeTag::TAG_STRING) { + return NativeValueConverter::FromNativeValue(element_->ctx(), dart_result); + } + } + + return value; } bool ElementAttributes::setAttribute(const AtomicString& name, @@ -47,7 +57,7 @@ bool ElementAttributes::setAttribute(const AtomicString& name, std::unique_ptr args_01 = name.ToNativeString(); std::unique_ptr args_02 = value.ToNativeString(); - GetExecutingContext()->uiCommandBuffer()->addCommand(owner_event_target_id_, UICommand::kSetAttribute, + GetExecutingContext()->uiCommandBuffer()->addCommand(element_->eventTargetId(), UICommand::kSetAttribute, std::move(args_01), std::move(args_02), nullptr); return true; @@ -67,7 +77,7 @@ void ElementAttributes::removeAttribute(const AtomicString& name, ExceptionState attributes_.erase(name); std::unique_ptr args_01 = name.ToNativeString(); - GetExecutingContext()->uiCommandBuffer()->addCommand(owner_event_target_id_, UICommand::kRemoveAttribute, + GetExecutingContext()->uiCommandBuffer()->addCommand(element_->eventTargetId(), UICommand::kRemoveAttribute, std::move(args_01), nullptr); } @@ -100,6 +110,8 @@ bool ElementAttributes::IsEquivalent(const ElementAttributes& other) const { return true; } -void ElementAttributes::Trace(GCVisitor* visitor) const {} +void ElementAttributes::Trace(GCVisitor* visitor) const { + visitor->Trace(element_); +} } // namespace webf diff --git a/bridge/core/dom/legacy/element_attributes.d.ts b/bridge/core/dom/legacy/element_attributes.d.ts index b5ffd4b28a..ef3738f1a3 100644 --- a/bridge/core/dom/legacy/element_attributes.d.ts +++ b/bridge/core/dom/legacy/element_attributes.d.ts @@ -1,5 +1,6 @@ export interface ElementAttributes { // Legacy methods: these methods are not W3C standard. + getAttribute(name: string): string; setAttribute(name: string, value: string): void; hasAttribute(name: string): boolean; removeAttribute(name: string): void; diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index 1c4d05fbf5..fcb16f6c9c 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -8,6 +8,7 @@ #include #include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/cppgc/member.h" #include "bindings/qjs/script_wrappable.h" #include "space_split_string.h" @@ -27,7 +28,7 @@ class ElementAttributes : public ScriptWrappable { explicit ElementAttributes(Element* element); - AtomicString GetAttribute(const AtomicString& name); + AtomicString getAttribute(const AtomicString& name, ExceptionState& exception_state); bool setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state); bool hasAttribute(const AtomicString& name, ExceptionState& exception_state); void removeAttribute(const AtomicString& name, ExceptionState& exception_state); @@ -39,7 +40,7 @@ class ElementAttributes : public ScriptWrappable { void Trace(GCVisitor* visitor) const override; private: - int32_t owner_event_target_id_; + Member element_; std::unordered_map attributes_; }; diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index bf668b81b4..f1156b15a5 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -13,6 +13,7 @@ #include "empty_node_list.h" #include "node_data.h" #include "node_traversal.h" +#include "qjs_node.h" #include "text.h" namespace webf { @@ -89,6 +90,10 @@ NodeData& Node::EnsureNodeData() { return CreateNodeData(); } +bool Node::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSNode::IsAttributeDefinedInternal(key) || EventTarget::IsAttributeDefinedInternal(key); +} + Node& Node::TreeRoot() const { const Node* node = this; while (node->parentNode()) diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 5ce44af936..d807270c47 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -227,6 +227,8 @@ class Node : public EventTarget { [[nodiscard]] NodeData* Data() const { return node_data_.get(); } NodeData& EnsureNodeData(); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + void Trace(GCVisitor*) const override; private: diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc index 6a02710cba..c67ce181b1 100644 --- a/bridge/core/dom/scripted_animation_controller.cc +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -12,7 +12,7 @@ static void handleRAFTransientCallback(void* ptr, int32_t contextId, double high auto* frame_callback = static_cast(ptr); auto* context = frame_callback->context(); - if (!context->IsValid()) + if (!context->IsContextValid()) return; if (errmsg != nullptr) { diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc index 24cd6cf72a..4fc16d6e6f 100644 --- a/bridge/core/events/message_event.cc +++ b/bridge/core/events/message_event.cc @@ -38,10 +38,20 @@ MessageEvent::MessageEvent(ExecutingContext* context, const AtomicString& type, NativeMessageEvent* native_message_event) : Event(context, type, &native_message_event->native_event), +#if ANDROID_32_BIT + data_(ScriptValue(ctx(), *(reinterpret_cast(native_message_event->data)))), + origin_(AtomicString(ctx(), reinterpret_cast(native_message_event->origin))), + lastEventId_(AtomicString(ctx(), reinterpret_cast(native_message_event->lastEventId))), + source_(AtomicString(ctx(), reinterpret_cast(native_message_event->source))) +#else data_(ScriptValue(ctx(), *(static_cast(native_message_event->data)))), origin_(AtomicString(ctx(), native_message_event->origin)), lastEventId_(AtomicString(ctx(), native_message_event->lastEventId)), - source_(AtomicString(ctx(), native_message_event->source)) {} + source_(AtomicString(ctx(), native_message_event->source)) +#endif + +{ +} ScriptValue MessageEvent::data() const { return data_; diff --git a/bridge/core/events/pop_state_event.cc b/bridge/core/events/pop_state_event.cc index 332c56b0a8..da902dc9c2 100644 --- a/bridge/core/events/pop_state_event.cc +++ b/bridge/core/events/pop_state_event.cc @@ -52,6 +52,7 @@ bool PopStateEvent::IsPopstateEvent() const { } void PopStateEvent::Trace(GCVisitor* visitor) const { + state_.Trace(visitor); Event::Trace(visitor); } diff --git a/bridge/core/events/wheel_event.d.ts b/bridge/core/events/wheel_event.d.ts deleted file mode 100644 index 5107e172ab..0000000000 --- a/bridge/core/events/wheel_event.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {MouseEvent} from "./mouse_event"; -import {WheelEventInit} from "./wheel_event_init"; -/** Events that occur due to the user moving a mouse wheel or similar input device. */ -interface WheelEvent extends MouseEvent { - readonly deltaMode: number; - readonly deltaX: number; - readonly deltaY: number; - readonly deltaZ: number; - readonly DOM_DELTA_LINE: StaticMember; - readonly DOM_DELTA_PAGE: StaticMember; - readonly DOM_DELTA_PIXEL: StaticMember; - new(type: string, init?: WheelEventInit): WheelEvent; -} diff --git a/bridge/core/events/wheel_event_init.d.ts b/bridge/core/events/wheel_event_init.d.ts deleted file mode 100644 index 37ad6a5000..0000000000 --- a/bridge/core/events/wheel_event_init.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {EventInit} from "../dom/events/event_init"; - -// @ts-ignore -@Dictionary() -export interface WheelEventInit extends EventInit { - deltaMode?: number; - deltaX?: number; - deltaY?: number; - deltaZ?: number; -} \ No newline at end of file diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 8b7364ef74..57fe2059e3 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -3,6 +3,8 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "executing_context.h" + +#include #include "bindings/qjs/converter_impl.h" #include "built_in_string.h" #include "core/dom/document.h" @@ -22,12 +24,17 @@ static std::atomic context_unique_id{0}; bool valid_contexts[MAX_JS_CONTEXT]; std::atomic running_context_list{0}; -std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) { - return std::make_unique(contextId, handler, owner); -} - -ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) - : context_id_(contextId), handler_(handler), owner_(owner), unique_id_(context_unique_id++) { +ExecutingContext::ExecutingContext(int32_t contextId, + JSExceptionHandler handler, + void* owner, + const uint64_t* dart_methods, + int32_t dart_methods_length) + : context_id_(contextId), + handler_(std::move(handler)), + owner_(owner), + unique_id_(context_unique_id++), + is_context_valid_(true), + dart_method_ptr_(std::make_unique(dart_methods, dart_methods_length)) { // #if ENABLE_PROFILE // auto jsContextStartTime = // std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) @@ -80,6 +87,7 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& } ExecutingContext::~ExecutingContext() { + is_context_valid_ = false; valid_contexts[context_id_] = false; // Check if current context have unhandled exceptions. @@ -143,7 +151,11 @@ bool ExecutingContext::EvaluateByteCode(uint8_t* bytes, size_t byteLength) { return true; } -bool ExecutingContext::IsValid() const { +bool ExecutingContext::IsContextValid() const { + return is_context_valid_; +} + +bool ExecutingContext::IsCtxValid() const { return script_state_.Invalid(); } @@ -168,11 +180,22 @@ bool ExecutingContext::HandleException(ScriptValue* exc) { return HandleException(&value); } +bool ExecutingContext::HandleException(ExceptionState& exception_state) { + if (exception_state.HasException()) { + JSValue error = JS_GetException(ctx()); + ReportError(error); + JS_FreeValue(ctx(), error); + return false; + } + return true; +} + JSValue ExecutingContext::Global() { return global_object_; } JSContext* ExecutingContext::ctx() { + assert(IsCtxValid()); return script_state_.ctx(); } diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 558bb3a797..5b01de23ee 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -41,6 +41,7 @@ class Window; class Performance; class MemberMutationScope; class ErrorEvent; +class ScriptWrappable; using JSExceptionHandler = std::function; @@ -52,7 +53,11 @@ bool isContextValid(int32_t contextId); class ExecutingContext { public: ExecutingContext() = delete; - ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); + ExecutingContext(int32_t contextId, + JSExceptionHandler handler, + void* owner, + const uint64_t* dart_methods, + int32_t dart_methods_length); ~ExecutingContext(); static ExecutingContext* From(JSContext* ctx); @@ -61,7 +66,8 @@ class ExecutingContext { bool EvaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine); bool EvaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine); bool EvaluateByteCode(uint8_t* bytes, size_t byteLength); - bool IsValid() const; + bool IsContextValid() const; + bool IsCtxValid() const; JSValue Global(); JSContext* ctx(); FORCE_INLINE int32_t contextId() const { return context_id_; }; @@ -69,6 +75,7 @@ class ExecutingContext { void* owner(); bool HandleException(JSValue* exc); bool HandleException(ScriptValue* exc); + bool HandleException(ExceptionState& exception_state); void ReportError(JSValueConst error); void DrainPendingPromiseJobs(); void DefineGlobalProperty(const char* prop, JSValueConst value); @@ -105,7 +112,7 @@ class ExecutingContext { FORCE_INLINE Window* window() const { return window_; } FORCE_INLINE Performance* performance() const { return performance_; } FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &ui_command_buffer_; }; - FORCE_INLINE std::unique_ptr& dartMethodPtr() { return dart_method_ptr_; } + FORCE_INLINE const std::unique_ptr& dartMethodPtr() { return dart_method_ptr_; } FORCE_INLINE std::chrono::time_point timeOrigin() const { return time_origin_; } // Force dart side to execute the pending ui commands. @@ -136,14 +143,25 @@ class ExecutingContext { JSValueConst reason, JS_BOOL is_handled, void* opaque); - - // Dart methods ptr should keep alive when ExecutingContext is disposing. - std::unique_ptr dart_method_ptr_ = std::make_unique(); + // Warning: Don't change the orders of members in ExecutingContext if you really know what are you doing. // From C++ standard, https://isocpp.org/wiki/faq/dtors#order-dtors-for-members // Members first initialized and destructed at the last. - // Always keep ScriptState at the top of all stack allocated members to make sure it destructed in the last. + // Dart methods ptr should keep alive when ExecutingContext is disposing. + const std::unique_ptr dart_method_ptr_ = nullptr; + // Keep uiCommandBuffer below dartMethod ptr to make sure we can flush all disposeEventTarget when UICommandBuffer + // release. + UICommandBuffer ui_command_buffer_{this}; + // 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 + // will be fill up to UICommand::disposeEventTarget commands. + // ---------------------------------------------------------------------- + // All members above ScriptState will be freed after ScriptState freed + // ---------------------------------------------------------------------- ScriptState script_state_; - + // ---------------------------------------------------------------------- + // All members below will be free before ScriptState freed. + // ---------------------------------------------------------------------- + bool is_context_valid_{false}; int32_t context_id_; JSExceptionHandler handler_; void* owner_; @@ -156,7 +174,6 @@ class ExecutingContext { ModuleCallbackCoordinator module_callbacks_; ExecutionContextData context_data_{this}; bool in_dispatch_error_event_{false}; - UICommandBuffer ui_command_buffer_{this}; RejectedPromises rejected_promises_; MemberMutationScope* active_mutation_scope{nullptr}; std::vector active_wrappers_; @@ -180,8 +197,6 @@ class ObjectProperty { JSValue m_value{JS_NULL}; }; -std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); - } // namespace webf #endif // BRIDGE_JS_CONTEXT_H diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 8e1f981473..e20dd9caf2 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -12,11 +12,13 @@ using namespace webf; TEST(Context, isValid) { { auto bridge = TEST_init(); - EXPECT_EQ(bridge->GetExecutingContext()->IsValid(), true); + EXPECT_EQ(bridge->GetExecutingContext()->IsContextValid(), true); + EXPECT_EQ(bridge->GetExecutingContext()->IsCtxValid(), true); } { auto bridge = TEST_init(); - EXPECT_EQ(bridge->GetExecutingContext()->IsValid(), true); + EXPECT_EQ(bridge->GetExecutingContext()->IsContextValid(), true); + EXPECT_EQ(bridge->GetExecutingContext()->IsCtxValid(), true); } } @@ -280,8 +282,8 @@ TEST(Context, accessGetUICommandItemsAfterDisposed) { } TEST(Context, disposeContext) { - initJSPagePool(1024 * 1024); - TEST_mockDartMethods(0, nullptr); + auto mockedDartMethods = TEST_getMockDartMethods(nullptr); + initJSPagePool(1024 * 1024, mockedDartMethods.data(), mockedDartMethods.size()); uint32_t contextId = 0; auto bridge = static_cast(getPage(contextId)); static bool disposed = false; diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index 8b3f79e309..9540c54591 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -33,7 +33,7 @@ static void handleTransientCallback(void* ptr, int32_t context_id, const char* e auto* timer = static_cast(ptr); auto* context = timer->context(); - if (!context->IsValid()) + if (!context->IsContextValid()) return; handleTimerCallback(timer, errmsg); diff --git a/bridge/core/frame/module_listener_container.cc b/bridge/core/frame/module_listener_container.cc index f1c78b23dc..7d0a25eaf0 100644 --- a/bridge/core/frame/module_listener_container.cc +++ b/bridge/core/frame/module_listener_container.cc @@ -3,15 +3,27 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "module_listener_container.h" +#include "bindings/qjs/cppgc/gc_visitor.h" namespace webf { -void ModuleListenerContainer::AddModuleListener(const std::shared_ptr& listener) { - listeners_.push_front(listener); +void ModuleListenerContainer::AddModuleListener(const AtomicString& name, + const std::shared_ptr& listener) { + listeners_[name] = listener; } -const std::forward_list>& ModuleListenerContainer::listeners() const { - return listeners_; +void ModuleListenerContainer::RemoveModuleListener(const AtomicString& name) { + listeners_.erase(name); +} + +std::shared_ptr ModuleListenerContainer::listener(const AtomicString& name) { + if (listeners_.count(name) == 0) + return nullptr; + return listeners_[name]; +} + +void ModuleListenerContainer::Clear() { + listeners_.clear(); } } // namespace webf diff --git a/bridge/core/frame/module_listener_container.h b/bridge/core/frame/module_listener_container.h index f214964e4e..a48c7c2c59 100644 --- a/bridge/core/frame/module_listener_container.h +++ b/bridge/core/frame/module_listener_container.h @@ -5,19 +5,20 @@ #ifndef BRIDGE_MODULE_LISTENER_CONTAINER_H #define BRIDGE_MODULE_LISTENER_CONTAINER_H -#include +#include #include "module_listener.h" namespace webf { class ModuleListenerContainer final { public: - void AddModuleListener(const std::shared_ptr& listener); - - const std::forward_list>& listeners() const; + void AddModuleListener(const AtomicString& name, const std::shared_ptr& listener); + void RemoveModuleListener(const AtomicString& name); + std::shared_ptr listener(const AtomicString& name); + void Clear(); private: - std::forward_list> listeners_; + std::unordered_map, AtomicString::KeyHasher> listeners_; friend ModuleListener; }; diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index e8522bae71..498c4cf535 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -13,116 +13,139 @@ struct ModuleContext { std::shared_ptr callback; }; -void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const char* errmsg, NativeString* json) { +NativeValue* handleInvokeModuleTransientCallback(void* ptr, + int32_t contextId, + const char* errmsg, + NativeValue* extra_data) { auto* moduleContext = static_cast(ptr); ExecutingContext* context = moduleContext->context; - if (!context->IsValid()) - return; + if (!context->IsContextValid()) + return nullptr; if (moduleContext->callback == nullptr) { JSValue exception = JS_ThrowTypeError(moduleContext->context->ctx(), "Failed to execute '__webf_invoke_module__': callback is null."); context->HandleException(&exception); - return; + return nullptr; } JSContext* ctx = moduleContext->context->ctx(); + ExceptionState exception_state; + NativeValue* return_value = nullptr; if (errmsg != nullptr) { - ScriptValue errorObject = ScriptValue::CreateErrorObject(ctx, errmsg); - ScriptValue arguments[] = {errorObject}; - ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, ScriptValue::Empty(ctx), 1, arguments); - if (returnValue.IsException()) { - context->HandleException(&returnValue); + ScriptValue error_object = ScriptValue::CreateErrorObject(ctx, errmsg); + ScriptValue arguments[] = {error_object}; + ScriptValue result = moduleContext->callback->value()->Invoke(ctx, ScriptValue::Empty(ctx), 1, arguments); + if (result.IsException()) { + context->HandleException(&result); } + NativeValue native_result = result.ToNative(exception_state); + return_value = static_cast(malloc(sizeof(NativeValue))); + memcpy(return_value, &native_result, sizeof(NativeValue)); } else { - std::u16string argumentString = std::u16string(reinterpret_cast(json->string()), json->length()); - std::string utf8Arguments = toUTF8(argumentString); - ScriptValue jsonObject = ScriptValue::CreateJsonObject(ctx, utf8Arguments.c_str(), utf8Arguments.size()); - ScriptValue arguments[] = {ScriptValue::Empty(ctx), jsonObject}; - ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, ScriptValue::Empty(ctx), 2, arguments); - if (returnValue.IsException()) { - context->HandleException(&returnValue); + ScriptValue arguments[] = {ScriptValue::Empty(ctx), ScriptValue(ctx, *extra_data)}; + ScriptValue result = moduleContext->callback->value()->Invoke(ctx, ScriptValue::Empty(ctx), 2, arguments); + if (result.IsException()) { + context->HandleException(&result); } + NativeValue native_result = result.ToNative(exception_state); + return_value = static_cast(malloc(sizeof(NativeValue))); + memcpy(return_value, &native_result, sizeof(NativeValue)); } - context->DrainPendingPromiseJobs(); - context->ModuleCallbacks()->RemoveModuleCallbacks(moduleContext->callback); + if (exception_state.HasException()) { + context->HandleException(exception_state); + return nullptr; + } + context->ModuleCallbacks()->RemoveModuleCallbacks(moduleContext->callback); delete moduleContext; + + return return_value; } -void handleInvokeModuleUnexpectedCallback(void* callbackContext, - int32_t contextId, - const char* errmsg, - NativeString* json) { +NativeValue* handleInvokeModuleUnexpectedCallback(void* callbackContext, + int32_t contextId, + const char* errmsg, + NativeValue* extra_data) { static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); + return nullptr; } -AtomicString ModuleManager::__webf_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ExceptionState& exception) { +ScriptValue ModuleManager::__webf_invoke_module__(ExecutingContext* context, + const AtomicString& module_name, + const AtomicString& method, + ExceptionState& exception) { ScriptValue empty = ScriptValue::Empty(context->ctx()); - return __webf_invoke_module__(context, moduleName, method, empty, nullptr, exception); + return __webf_invoke_module__(context, module_name, method, empty, nullptr, exception); } -AtomicString ModuleManager::__webf_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ScriptValue& paramsValue, - ExceptionState& exception) { - return __webf_invoke_module__(context, moduleName, method, paramsValue, nullptr, exception); +ScriptValue ModuleManager::__webf_invoke_module__(ExecutingContext* context, + const AtomicString& module_name, + const AtomicString& method, + ScriptValue& params_value, + ExceptionState& exception) { + return __webf_invoke_module__(context, module_name, method, params_value, nullptr, exception); } -AtomicString ModuleManager::__webf_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ScriptValue& paramsValue, - std::shared_ptr callback, - ExceptionState& exception) { - std::unique_ptr params; - if (!paramsValue.IsEmpty()) { - params = paramsValue.ToJSONStringify(&exception).ToString().ToNativeString(); - if (exception.HasException()) { - return AtomicString::Empty(); - } +ScriptValue ModuleManager::__webf_invoke_module__(ExecutingContext* context, + const AtomicString& module_name, + const AtomicString& method, + ScriptValue& params_value, + std::shared_ptr callback, + ExceptionState& exception) { + NativeValue params = params_value.ToNative(exception); + + if (exception.HasException()) { + 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 AtomicString::Empty(); + return ScriptValue::Empty(context->ctx()); } - NativeString* result; + NativeValue* result; if (callback != nullptr) { auto moduleCallback = ModuleCallback::Create(callback); context->ModuleCallbacks()->AddModuleCallbacks(std::move(moduleCallback)); - ModuleContext* moduleContext = new ModuleContext{context, moduleCallback}; + auto* moduleContext = new ModuleContext{context, moduleCallback}; result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), - moduleName.ToNativeString().get(), method.ToNativeString().get(), - params.get(), handleInvokeModuleTransientCallback); + module_name.ToNativeString().get(), method.ToNativeString().get(), + ¶ms, handleInvokeModuleTransientCallback); } else { - result = context->dartMethodPtr()->invokeModule(nullptr, context->contextId(), moduleName.ToNativeString().get(), - method.ToNativeString().get(), params.get(), + result = context->dartMethodPtr()->invokeModule(nullptr, context->contextId(), module_name.ToNativeString().get(), + method.ToNativeString().get(), ¶ms, handleInvokeModuleUnexpectedCallback); } if (result == nullptr) { - return AtomicString::Empty(); + return ScriptValue::Empty(context->ctx()); } - return AtomicString::From(context->ctx(), result); + return ScriptValue(context->ctx(), *result); } void ModuleManager::__webf_add_module_listener__(ExecutingContext* context, + const AtomicString& module_name, const std::shared_ptr& handler, ExceptionState& exception) { auto listener = ModuleListener::Create(handler); - context->ModuleListeners()->AddModuleListener(listener); + context->ModuleListeners()->AddModuleListener(module_name, listener); +} + +void ModuleManager::__webf_remove_module_listener__(ExecutingContext* context, + const AtomicString& module_name, + ExceptionState& exception_state) { + context->ModuleListeners()->RemoveModuleListener(module_name); +} + +void ModuleManager::__webf_clear_module_listener__(ExecutingContext* context, ExceptionState& exception_state) { + context->ModuleListeners()->Clear(); } } // namespace webf diff --git a/bridge/core/frame/module_manager.d.ts b/bridge/core/frame/module_manager.d.ts index bf67fcd75f..c9e568a3c7 100644 --- a/bridge/core/frame/module_manager.d.ts +++ b/bridge/core/frame/module_manager.d.ts @@ -1,2 +1,4 @@ -declare const __webf_invoke_module__: (moduleName: string, methodName: string, paramsValue?: any, callback?: Function) => string; -declare const __webf_add_module_listener__: (callback: Function) => void; +declare const __webf_invoke_module__: (moduleName: string, methodName: string, paramsValue?: any, callback?: Function) => any; +declare const __webf_add_module_listener__: (moduleName: string, callback: Function) => void; +declare const __webf_remove_module_listener__: (moduleName: string) => void; +declare const __webf_clear_module_listener__: () => void; diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index 1eb30d8179..76c37efe95 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -14,24 +14,29 @@ namespace webf { class ModuleManager { public: - static AtomicString __webf_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ExceptionState& exception); - static AtomicString __webf_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ScriptValue& paramsValue, - ExceptionState& exception); - static AtomicString __webf_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ScriptValue& paramsValue, - std::shared_ptr callback, - ExceptionState& exception); + static ScriptValue __webf_invoke_module__(ExecutingContext* context, + const AtomicString& module_name, + const AtomicString& method, + ExceptionState& exception); + static ScriptValue __webf_invoke_module__(ExecutingContext* context, + const AtomicString& module_name, + const AtomicString& method, + ScriptValue& params_value, + ExceptionState& exception); + static ScriptValue __webf_invoke_module__(ExecutingContext* context, + const AtomicString& module_name, + const AtomicString& method, + ScriptValue& params_value, + std::shared_ptr callback, + ExceptionState& exception); static void __webf_add_module_listener__(ExecutingContext* context, + const AtomicString& module_name, const std::shared_ptr& handler, ExceptionState& exception); + static void __webf_remove_module_listener__(ExecutingContext* context, + const AtomicString& module_name, + ExceptionState& exception_state); + static void __webf_clear_module_listener__(ExecutingContext* context, ExceptionState& exception_state); }; } // namespace webf diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index dba592d565..960599f1bc 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -8,6 +8,7 @@ #include "bindings/qjs/cppgc/garbage_collected.h" #include "core/dom/document.h" #include "core/events/message_event.h" +#include "core/executing_context.h" #include "event_type_names.h" #include "foundation/native_value_converter.h" diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 0cd3292d6f..b6a0962ddc 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -28,7 +28,7 @@ static void handleTransientCallback(void* ptr, int32_t contextId, const char* er auto* timer = static_cast(ptr); auto* context = timer->context(); - if (!context->IsValid()) + if (!context->IsContextValid()) return; if (timer->status() == DOMTimer::TimerStatus::kCanceled) { @@ -46,7 +46,7 @@ static void handlePersistentCallback(void* ptr, int32_t contextId, const char* e auto* timer = static_cast(ptr); auto* context = timer->context(); - if (!context->IsValid()) + if (!context->IsContextValid()) return; if (timer->status() == DOMTimer::TimerStatus::kCanceled) { diff --git a/bridge/core/html/canvas/html_canvas_element.cc b/bridge/core/html/canvas/html_canvas_element.cc index be6c8029bf..fda87609fb 100644 --- a/bridge/core/html/canvas/html_canvas_element.cc +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -8,6 +8,7 @@ #include "canvas_types.h" #include "foundation/native_value_converter.h" #include "html_names.h" +#include "qjs_html_canvas_element.h" namespace webf { @@ -26,4 +27,8 @@ CanvasRenderingContext* HTMLCanvasElement::getContext(const AtomicString& type, return nullptr; } +bool HTMLCanvasElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLCanvasElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/canvas/html_canvas_element.h b/bridge/core/html/canvas/html_canvas_element.h index 3ed579b08d..32210e2ed1 100644 --- a/bridge/core/html/canvas/html_canvas_element.h +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -17,6 +17,8 @@ class HTMLCanvasElement : public HTMLElement { explicit HTMLCanvasElement(Document&); CanvasRenderingContext* getContext(const AtomicString& type, ExceptionState& exception_state) const; + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; }; } // namespace webf diff --git a/bridge/core/html/custom/widget_element.cc b/bridge/core/html/custom/widget_element.cc index 6474db3515..a08744c7b4 100644 --- a/bridge/core/html/custom/widget_element.cc +++ b/bridge/core/html/custom/widget_element.cc @@ -67,7 +67,7 @@ bool WidgetElement::SetItem(const AtomicString& key, const ScriptValue& value, E return true; } - NativeValue result = SetBindingProperty(key, value.ToNative(), exception_state); + NativeValue result = SetBindingProperty(key, value.ToNative(exception_state), exception_state); return NativeValueConverter::FromNativeValue(result); } @@ -89,4 +89,8 @@ void WidgetElement::CloneNonAttributePropertiesFrom(const Element& other, CloneC } } +bool WidgetElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return true; +} + } // namespace webf \ No newline at end of file diff --git a/bridge/core/html/custom/widget_element.h b/bridge/core/html/custom/widget_element.h index d1bac9e1cc..f240240703 100644 --- a/bridge/core/html/custom/widget_element.h +++ b/bridge/core/html/custom/widget_element.h @@ -35,6 +35,7 @@ class WidgetElement : public HTMLElement { void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) override; void Trace(GCVisitor* visitor) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; private: 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 new file mode 100644 index 0000000000..5f820e7816 --- /dev/null +++ b/bridge/core/html/custom/widget_element_test.cc @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "widget_element.h" +#include "gtest/gtest.h" +#include "webf_test_env.h" + +using namespace webf; + +TEST(WidgetElement, setPropertyEventHandler) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "1111"); + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let checkbox = document.createElement('flutter-checkbox'); " + "function f(){ console.log(1111); }; " + "checkbox.onclick = f; " + "checkbox.dispatchEvent(new Event('click'));"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} diff --git a/bridge/core/html/forms/html_button_element.cc b/bridge/core/html/forms/html_button_element.cc index 3d44078f1a..198e6895f4 100644 --- a/bridge/core/html/forms/html_button_element.cc +++ b/bridge/core/html/forms/html_button_element.cc @@ -4,3 +4,12 @@ */ #include "html_button_element.h" +#include "qjs_html_button_element.h" + +namespace webf { + +bool HTMLButtonElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLButtonElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/forms/html_button_element.h b/bridge/core/html/forms/html_button_element.h index 1d454a5be7..8b4bc65083 100644 --- a/bridge/core/html/forms/html_button_element.h +++ b/bridge/core/html/forms/html_button_element.h @@ -14,6 +14,8 @@ class HTMLButtonElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + private: }; diff --git a/bridge/core/html/forms/html_input_element.cc b/bridge/core/html/forms/html_input_element.cc index 86e7798e7e..83406b96f3 100644 --- a/bridge/core/html/forms/html_input_element.cc +++ b/bridge/core/html/forms/html_input_element.cc @@ -4,9 +4,14 @@ */ #include "html_input_element.h" #include "html_names.h" +#include "qjs_html_input_element.h" namespace webf { HTMLInputElement::HTMLInputElement(Document& document) : HTMLElement(html_names::kinput, &document) {} +bool HTMLInputElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLInputElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/forms/html_input_element.h b/bridge/core/html/forms/html_input_element.h index d1937c79fa..835f436f22 100644 --- a/bridge/core/html/forms/html_input_element.h +++ b/bridge/core/html/forms/html_input_element.h @@ -14,6 +14,8 @@ class HTMLInputElement : public HTMLElement { public: explicit HTMLInputElement(Document&); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; }; } // namespace webf diff --git a/bridge/core/html/forms/html_textarea_element.cc b/bridge/core/html/forms/html_textarea_element.cc index cbf6ea2cd5..b620d57f5b 100644 --- a/bridge/core/html/forms/html_textarea_element.cc +++ b/bridge/core/html/forms/html_textarea_element.cc @@ -4,9 +4,14 @@ */ #include "html_textarea_element.h" #include "html_names.h" +#include "qjs_html_textarea_element.h" namespace webf { HTMLTextareaElement::HTMLTextareaElement(Document& document) : HTMLElement(html_names::ktextarea, &document) {} +bool HTMLTextareaElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLTextareaElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/forms/html_textarea_element.d.ts b/bridge/core/html/forms/html_textarea_element.d.ts index 386344c7cf..5862f0e3bf 100644 --- a/bridge/core/html/forms/html_textarea_element.d.ts +++ b/bridge/core/html/forms/html_textarea_element.d.ts @@ -1,7 +1,7 @@ // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element import {HTMLElement} from "../html_element"; -interface HTMLTextAreaElement extends HTMLElement { +interface HTMLTextareaElement extends HTMLElement { defaultValue: DartImpl; value: DartImpl; cols: DartImpl; diff --git a/bridge/core/html/forms/html_textarea_element.h b/bridge/core/html/forms/html_textarea_element.h index 41ea84f071..91469be3e1 100644 --- a/bridge/core/html/forms/html_textarea_element.h +++ b/bridge/core/html/forms/html_textarea_element.h @@ -14,6 +14,8 @@ class HTMLTextareaElement : public HTMLElement { public: explicit HTMLTextareaElement(Document&); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; }; } // namespace webf diff --git a/bridge/core/html/html_anchor_element.cc b/bridge/core/html/html_anchor_element.cc index 1615384da8..8507159498 100644 --- a/bridge/core/html/html_anchor_element.cc +++ b/bridge/core/html/html_anchor_element.cc @@ -4,9 +4,14 @@ */ #include "html_anchor_element.h" #include "html_names.h" +#include "qjs_html_anchor_element.h" namespace webf { HTMLAnchorElement::HTMLAnchorElement(Document& document) : HTMLElement(html_names::ka, &document) {} +bool HTMLAnchorElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLAnchorElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_anchor_element.d.ts b/bridge/core/html/html_anchor_element.d.ts index 3b8ea28c11..fa3f09e8ba 100644 --- a/bridge/core/html/html_anchor_element.d.ts +++ b/bridge/core/html/html_anchor_element.d.ts @@ -1,6 +1,6 @@ import {Element} from "../dom/element"; -interface AnchorElement extends Element { +interface HTMLAnchorElement extends Element { target: DartImpl; accessKey: DartImpl; download: DartImpl; diff --git a/bridge/core/html/html_anchor_element.h b/bridge/core/html/html_anchor_element.h index 51a96c7858..9401092f08 100644 --- a/bridge/core/html/html_anchor_element.h +++ b/bridge/core/html/html_anchor_element.h @@ -15,6 +15,8 @@ class HTMLAnchorElement : public HTMLElement { public: explicit HTMLAnchorElement(Document& document); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + private: }; diff --git a/bridge/core/html/html_body_element.cc b/bridge/core/html/html_body_element.cc index f353b84393..bd5d5dd962 100644 --- a/bridge/core/html/html_body_element.cc +++ b/bridge/core/html/html_body_element.cc @@ -4,9 +4,14 @@ */ #include "html_body_element.h" #include "html_names.h" +#include "qjs_html_body_element.h" namespace webf { HTMLBodyElement::HTMLBodyElement(Document& document) : HTMLElement(html_names::kbody, &document) {} +bool HTMLBodyElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLBodyElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index b9927d0184..23589f4375 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -18,6 +18,8 @@ class HTMLBodyElement : public HTMLElement { using ImplType = HTMLBodyElement*; explicit HTMLBodyElement(Document&); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(blur, kblur); DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(error, kerror); DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(focus, kfocus); diff --git a/bridge/core/html/html_div_element.cc b/bridge/core/html/html_div_element.cc index f0bd24d58e..0f473324f7 100644 --- a/bridge/core/html/html_div_element.cc +++ b/bridge/core/html/html_div_element.cc @@ -5,9 +5,14 @@ #include "html_div_element.h" #include "html_names.h" +#include "qjs_html_div_element.h" namespace webf { HTMLDivElement::HTMLDivElement(Document& document) : HTMLElement(html_names::kdiv, &document) {} +bool HTMLDivElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLDivElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_div_element.h b/bridge/core/html/html_div_element.h index a58fa60e2c..be766ad08d 100644 --- a/bridge/core/html/html_div_element.h +++ b/bridge/core/html/html_div_element.h @@ -17,6 +17,8 @@ class HTMLDivElement : public HTMLElement { using ImplType = HTMLDivElement*; explicit HTMLDivElement(Document&); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + private: }; diff --git a/bridge/core/html/html_element.cc b/bridge/core/html/html_element.cc index 9570063f4c..3f87b9b830 100644 --- a/bridge/core/html/html_element.cc +++ b/bridge/core/html/html_element.cc @@ -4,5 +4,12 @@ */ #include "html_element.h" +#include "qjs_html_element.h" -namespace webf {} // namespace webf +namespace webf { + +bool HTMLElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLElement::IsAttributeDefinedInternal(key) || Element::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h index 410243cb9c..6b08186b63 100644 --- a/bridge/core/html/html_element.h +++ b/bridge/core/html/html_element.h @@ -18,6 +18,8 @@ class HTMLElement : public Element { using ImplType = HTMLElement*; HTMLElement(const AtomicString& tag_name, Document* document, ConstructionType); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + private: }; diff --git a/bridge/core/html/html_head_element.cc b/bridge/core/html/html_head_element.cc index 2c649bddf9..83bdb095a4 100644 --- a/bridge/core/html/html_head_element.cc +++ b/bridge/core/html/html_head_element.cc @@ -4,9 +4,14 @@ */ #include "html_head_element.h" #include "html_names.h" +#include "qjs_html_head_element.h" namespace webf { HTMLHeadElement::HTMLHeadElement(Document& document) : HTMLElement(html_names::khead, &document) {} +bool HTMLHeadElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLHeadElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_head_element.h b/bridge/core/html/html_head_element.h index ff5edf81ba..6213266a8d 100644 --- a/bridge/core/html/html_head_element.h +++ b/bridge/core/html/html_head_element.h @@ -16,6 +16,8 @@ class HTMLHeadElement : public HTMLElement { using ImplType = HTMLHeadElement*; explicit HTMLHeadElement(Document&); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + private: }; diff --git a/bridge/core/html/html_html_element.cc b/bridge/core/html/html_html_element.cc index cb3fd5e995..a89fd91076 100644 --- a/bridge/core/html/html_html_element.cc +++ b/bridge/core/html/html_html_element.cc @@ -4,9 +4,14 @@ */ #include "html_html_element.h" #include "html_names.h" +#include "qjs_html_html_element.h" namespace webf { HTMLHtmlElement::HTMLHtmlElement(Document& document) : HTMLElement(html_names::khtml, &document) {} +bool HTMLHtmlElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLHtmlElement::IsAttributeDefinedInternal(key) || HTMLHtmlElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_html_element.h b/bridge/core/html/html_html_element.h index e2ab318340..475106e2c1 100644 --- a/bridge/core/html/html_html_element.h +++ b/bridge/core/html/html_html_element.h @@ -16,6 +16,8 @@ class HTMLHtmlElement : public HTMLElement { using ImplType = HTMLHtmlElement*; explicit HTMLHtmlElement(Document&); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + private: }; diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index 912b5c0f4d..565c6059b3 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -4,11 +4,16 @@ */ #include "html_image_element.h" #include "html_names.h" +#include "qjs_html_image_element.h" namespace webf { HTMLImageElement::HTMLImageElement(Document& document) : HTMLElement(html_names::kimg, &document) {} +bool HTMLImageElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLImageElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + ScriptPromise HTMLImageElement::decode(ExceptionState& exception_state) const { exception_state.ThrowException(ctx(), ErrorType::InternalError, "Not implemented."); // @TODO not implemented. diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h index d61889478c..5b85deb40f 100644 --- a/bridge/core/html/html_image_element.h +++ b/bridge/core/html/html_image_element.h @@ -16,6 +16,8 @@ class HTMLImageElement : public HTMLElement { using ImplType = HTMLImageElement*; explicit HTMLImageElement(Document& document); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + bool KeepAlive() const override; ScriptPromise decode(ExceptionState& exception_state) const; diff --git a/bridge/core/html/html_link_element.cc b/bridge/core/html/html_link_element.cc new file mode 100644 index 0000000000..6459ca87ed --- /dev/null +++ b/bridge/core/html/html_link_element.cc @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "html_link_element.h" +#include "html_names.h" +#include "qjs_html_link_element.h" + +namespace webf { + +HTMLLinkElement::HTMLLinkElement(Document& document) : HTMLElement(html_names::klink, &document) {} + +bool HTMLLinkElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLLinkElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/html_link_element.d.ts b/bridge/core/html/html_link_element.d.ts new file mode 100644 index 0000000000..3f1f73e739 --- /dev/null +++ b/bridge/core/html/html_link_element.d.ts @@ -0,0 +1,9 @@ +import {HTMLElement} from "./html_element"; + +interface HTMLLinkElement extends HTMLElement { + disabled: DartImpl; + rel: DartImpl; + href: DartImpl; + type: DartImpl; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/html/html_link_element.h b/bridge/core/html/html_link_element.h new file mode 100644 index 0000000000..04800fdb82 --- /dev/null +++ b/bridge/core/html/html_link_element.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_HTML_HTML_LINK_ELEMENT_H_ +#define WEBF_CORE_HTML_HTML_LINK_ELEMENT_H_ + +#include "html_element.h" + +namespace webf { + +class HTMLLinkElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLLinkElement(Document& document); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + private: +}; + +} // namespace webf + +#endif // WEBF_CORE_HTML_HTML_LINK_ELEMENT_H_ diff --git a/bridge/core/html/html_script_element.cc b/bridge/core/html/html_script_element.cc index 5ea117a4f7..42a564f412 100644 --- a/bridge/core/html/html_script_element.cc +++ b/bridge/core/html/html_script_element.cc @@ -4,6 +4,7 @@ */ #include "html_script_element.h" #include "html_names.h" +#include "qjs_html_script_element.h" #include "script_type_names.h" namespace webf { @@ -18,4 +19,8 @@ bool HTMLScriptElement::supports(const AtomicString& type, ExceptionState& excep return false; } +bool HTMLScriptElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLScriptElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_script_element.h b/bridge/core/html/html_script_element.h index 4e28efbb31..c02142cb69 100644 --- a/bridge/core/html/html_script_element.h +++ b/bridge/core/html/html_script_element.h @@ -17,6 +17,8 @@ class HTMLScriptElement : public HTMLElement { explicit HTMLScriptElement(Document& document); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + private: }; diff --git a/bridge/core/html/html_tag_names.json5 b/bridge/core/html/html_tag_names.json5 index a6710aa070..9134cad299 100644 --- a/bridge/core/html/html_tag_names.json5 +++ b/bridge/core/html/html_tag_names.json5 @@ -32,6 +32,11 @@ "body", "head", "div", + { + "name": "link", + "interfaceName": "HTMLLinkElement", + "filename": "html_link_element" + }, { "name": "input", "interfaceHeaderDir": "core/html/forms" diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc index bb030a75fe..e00cea764b 100644 --- a/bridge/core/html/html_template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -5,6 +5,7 @@ #include "html_template_element.h" #include "core/dom/document_fragment.h" #include "html_names.h" +#include "qjs_html_template_element.h" namespace webf { @@ -21,4 +22,8 @@ DocumentFragment* HTMLTemplateElement::ContentInternal() const { return content_.Get(); } +bool HTMLTemplateElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLTemplateElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h index 3aa5dd8cfe..da93a6b275 100644 --- a/bridge/core/html/html_template_element.h +++ b/bridge/core/html/html_template_element.h @@ -19,6 +19,8 @@ class HTMLTemplateElement : public HTMLElement { DocumentFragment* content() const; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + private: DocumentFragment* ContentInternal() const; mutable Member content_; diff --git a/bridge/core/html/html_unknown_element.cc b/bridge/core/html/html_unknown_element.cc index dfe858b782..f378590723 100644 --- a/bridge/core/html/html_unknown_element.cc +++ b/bridge/core/html/html_unknown_element.cc @@ -3,10 +3,15 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "html_unknown_element.h" +#include "qjs_html_unknown_element.h" namespace webf { HTMLUnknownElement::HTMLUnknownElement(const AtomicString& tag_name, Document& document) : HTMLElement(tag_name, &document) {} +bool HTMLUnknownElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLUnknownElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_unknown_element.h b/bridge/core/html/html_unknown_element.h index c98ea6b9fb..3ead0006d1 100644 --- a/bridge/core/html/html_unknown_element.h +++ b/bridge/core/html/html_unknown_element.h @@ -15,6 +15,8 @@ class HTMLUnknownElement : public HTMLElement { public: explicit HTMLUnknownElement(const AtomicString&, Document& document); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + private: }; diff --git a/bridge/core/html/image.cc b/bridge/core/html/image.cc index 7fae11aa82..385ae3fd3a 100644 --- a/bridge/core/html/image.cc +++ b/bridge/core/html/image.cc @@ -3,6 +3,7 @@ */ #include "image.h" +#include "qjs_image.h" namespace webf { @@ -12,4 +13,8 @@ Image* Image::Create(ExecutingContext* context, ExceptionState& exception_state) Image::Image(ExecutingContext* context, ExceptionState& exception_state) : HTMLImageElement(*context->document()) {} +bool Image::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSImage::IsAttributeDefinedInternal(key) || HTMLImageElement::IsAttributeDefinedInternal(key); +} + } // namespace webf \ No newline at end of file diff --git a/bridge/core/html/image.h b/bridge/core/html/image.h index d80200f9e0..cdc62d02fc 100644 --- a/bridge/core/html/image.h +++ b/bridge/core/html/image.h @@ -17,6 +17,8 @@ class Image : public HTMLImageElement { explicit Image(ExecutingContext* context, ExceptionState& exception_state); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + private: }; diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 651825e1e2..48d234d0a0 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -5,6 +5,7 @@ #include #include +#include "bindings/qjs/atomic_string.h" #include "bindings/qjs/binding_initializer.h" #include "core/dart_methods.h" #include "core/dom/document.h" @@ -22,7 +23,10 @@ ConsoleMessageHandler WebFPage::consoleMessageHandler{nullptr}; webf::WebFPage** WebFPage::pageContextPool{nullptr}; -WebFPage::WebFPage(int32_t contextId, const JSExceptionHandler& handler) +WebFPage::WebFPage(int32_t contextId, + const JSExceptionHandler& handler, + const uint64_t* dart_methods, + int32_t dart_methods_length) : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { context_ = new ExecutingContext( contextId, @@ -32,11 +36,11 @@ WebFPage::WebFPage(int32_t contextId, const JSExceptionHandler& handler) } WEBF_LOG(ERROR) << message << std::endl; }, - this); + this, dart_methods, dart_methods_length); } bool WebFPage::parseHTML(const char* code, size_t length) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return false; MemberMutationScope scope{context_}; @@ -46,20 +50,17 @@ bool WebFPage::parseHTML(const char* code, size_t length) { return false; } - // Remove all Nodes including body and head. - context_->document()->documentElement()->RemoveChildren(); - HTMLParser::parseHTML(code, length, context_->document()->documentElement()); return true; } -void WebFPage::invokeModuleEvent(const NativeString* moduleName, - const char* eventType, - void* ptr, - NativeString* extra) { - if (!context_->IsValid()) - return; +NativeValue* WebFPage::invokeModuleEvent(const NativeString* native_module_name, + const char* eventType, + void* ptr, + NativeValue* extra) { + if (!context_->IsContextValid()) + return nullptr; MemberMutationScope scope{context_}; @@ -71,26 +72,35 @@ void WebFPage::invokeModuleEvent(const NativeString* moduleName, event = EventFactory::Create(context_, AtomicString(ctx, type), rawEvent); } - ScriptValue extraObject = ScriptValue::Empty(ctx); - if (extra != nullptr) { - std::u16string u16Extra = std::u16string(reinterpret_cast(extra->string()), extra->length()); - std::string extraString = toUTF8(u16Extra); - extraObject = ScriptValue::CreateJsonObject(ctx, extraString.c_str(), extraString.length()); + ScriptValue extraObject = ScriptValue(ctx, *extra); + AtomicString module_name = AtomicString(ctx, native_module_name); + auto listener = context_->ModuleListeners()->listener(module_name); + + if (listener == nullptr) { + return nullptr; } - auto listeners = context_->ModuleListeners()->listeners(); - for (auto& listener : listeners) { - ScriptValue arguments[] = {ScriptValue(ctx, moduleName), - event != nullptr ? event->ToValue() : ScriptValue::Empty(ctx), extraObject}; - ScriptValue result = listener->value()->Invoke(ctx, ScriptValue::Empty(ctx), 3, arguments); - if (result.IsException()) { - context_->HandleException(&result); - } + ScriptValue arguments[] = {event != nullptr ? event->ToValue() : ScriptValue::Empty(ctx), extraObject}; + ScriptValue result = listener->value()->Invoke(ctx, ScriptValue::Empty(ctx), 2, arguments); + if (result.IsException()) { + context_->HandleException(&result); + return nullptr; } + + ExceptionState exception_state; + auto* return_value = static_cast(malloc(sizeof(NativeValue))); + NativeValue tmp = result.ToNative(exception_state); + if (exception_state.HasException()) { + context_->HandleException(exception_state); + return nullptr; + } + + memcpy(return_value, &tmp, sizeof(NativeValue)); + return return_value; } void WebFPage::evaluateScript(const NativeString* script, const char* url, int startLine) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return; //#if ENABLE_PROFILE @@ -105,57 +115,29 @@ void WebFPage::evaluateScript(const NativeString* script, const char* url, int s } void WebFPage::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return; context_->EvaluateJavaScript(script, length, url, startLine); } void WebFPage::evaluateScript(const char* script, size_t length, const char* url, int startLine) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return; context_->EvaluateJavaScript(script, length, url, startLine); } uint8_t* WebFPage::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return nullptr; return context_->DumpByteCode(script, length, url, byteLength); } void WebFPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return; context_->EvaluateByteCode(bytes, byteLength); } -void WebFPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { - size_t i = 0; - - auto& dartMethodPointer = context_->dartMethodPtr(); - - dartMethodPointer->invokeModule = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->requestBatchUpdate = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->reloadApp = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->setTimeout = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->setInterval = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->clearTimeout = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->requestAnimationFrame = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->cancelAnimationFrame = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->toBlob = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->flushUICommand = reinterpret_cast(methodBytes[i++]); - -#if ENABLE_PROFILE - dartMethodPointer->getPerformanceEntries = reinterpret_cast(methodBytes[i++]); -#else - i++; -#endif - - dartMethodPointer->onJsError = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->onJsLog = reinterpret_cast(methodBytes[i++]); - - assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); -} - std::thread::id WebFPage::currentThread() const { return ownerThreadId; } diff --git a/bridge/core/page.h b/bridge/core/page.h index 4e4e3eaca4..671cfd711a 100644 --- a/bridge/core/page.h +++ b/bridge/core/page.h @@ -32,7 +32,10 @@ class WebFPage final { static webf::WebFPage** pageContextPool; static ConsoleMessageHandler consoleMessageHandler; WebFPage() = delete; - WebFPage(int32_t jsContext, const JSExceptionHandler& handler); + WebFPage(int32_t jsContext, + const JSExceptionHandler& handler, + const uint64_t* dart_methods, + int32_t dart_methods_length); ~WebFPage(); // Bytecodes which registered by webf plugins. @@ -46,12 +49,14 @@ class WebFPage final { uint8_t* dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength); void evaluateByteCode(uint8_t* bytes, size_t byteLength); - void registerDartMethods(uint64_t* methodBytes, int32_t length); std::thread::id currentThread() const; [[nodiscard]] ExecutingContext* GetExecutingContext() const { return context_; } - void invokeModuleEvent(const NativeString* moduleName, const char* eventType, void* event, NativeString* extra); + NativeValue* invokeModuleEvent(const NativeString* moduleName, + const char* eventType, + void* event, + NativeValue* extra); void reportError(const char* errmsg); int32_t contextId; diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index f5a7088082..6a1b9cb125 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -3,14 +3,15 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "script_state.h" +#include "defined_properties_initializer.h" #include "event_factory.h" #include "html_element_factory.h" #include "names_installer.h" namespace webf { -JSRuntime* runtime_ = nullptr; -std::atomic runningContexts{0}; +thread_local JSRuntime* runtime_ = nullptr; +thread_local std::atomic runningContexts{0}; ScriptState::ScriptState() { runningContexts++; @@ -25,6 +26,7 @@ ScriptState::ScriptState() { if (first_loaded) { names_installer::Init(ctx_); + DefinedPropertiesInitializer::Init(); // Bump up the built-in classId. To make sure the created classId are larger than JS_CLASS_CUSTOM_CLASS_INIT_COUNT. for (int i = 0; i < JS_CLASS_CUSTOM_CLASS_INIT_COUNT - JS_CLASS_GC_TRACKER + 2; i++) { JSClassID id{0}; @@ -46,6 +48,7 @@ ScriptState::~ScriptState() { if (--runningContexts == 0) { // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. + DefinedPropertiesInitializer::Dispose(); names_installer::Dispose(); HTMLElementFactory::Dispose(); EventFactory::Dispose(); diff --git a/bridge/core/script_state.h b/bridge/core/script_state.h index 1e154e29b6..edcfbcb53a 100644 --- a/bridge/core/script_state.h +++ b/bridge/core/script_state.h @@ -6,7 +6,7 @@ #define BRIDGE_CORE_SCRIPT_STATE_H_ #include -#include "bindings/qjs/script_wrappable.h" +#include namespace webf { diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 6fef0deb03..51a707b219 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -62,8 +62,7 @@ NativeValue Native_NewList(uint32_t argc, NativeValue* argv) { return (NativeValue){.u = {.ptr = reinterpret_cast(argv)}, .uint32 = argc, .tag = NativeTag::TAG_LIST}; } -NativeValue Native_NewJSON(const ScriptValue& value) { - ExceptionState exception_state; +NativeValue Native_NewJSON(const ScriptValue& value, ExceptionState& exception_state) { ScriptValue json = value.ToJSONStringify(&exception_state); if (exception_state.HasException()) { return Native_NewNull(); diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index b49ef2ef94..1c98c9385c 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -30,6 +30,7 @@ enum NativeTag { enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, Others = 2 }; class ExecutingContext; +class ExceptionState; class ScriptValue; // Exchange data struct between dart and C++ @@ -73,7 +74,7 @@ NativeValue Native_NewBool(bool value); NativeValue Native_NewInt64(int64_t value); NativeValue Native_NewList(uint32_t argc, NativeValue* argv); NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr); -NativeValue Native_NewJSON(const ScriptValue& value); +NativeValue Native_NewJSON(const ScriptValue& value, ExceptionState& exception_state); } // namespace webf diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 63b288ad1e..0f853e57cd 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -78,7 +78,9 @@ struct NativeValueConverter : public NativeValueConverterBase< template <> struct NativeValueConverter : public NativeValueConverterBase { - static NativeValue ToNativeValue(ImplType value) { return Native_NewJSON(value); } + static NativeValue ToNativeValue(ImplType value, ExceptionState& exception_state) { + return Native_NewJSON(value, exception_state); + } static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { assert(value.tag == NativeTag::TAG_JSON); auto* str = static_cast(value.u.ptr); diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 5fa030ba7b..6bd6f9cbad 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -13,6 +13,15 @@ namespace webf { UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context) {} +UICommandBuffer::~UICommandBuffer() { +#if FLUTTER_BACKEND + // Flush and execute all disposeEventTarget commands when context released. + if (context_->dartMethodPtr()->flushUICommand != nullptr && !isDartHotRestart()) { + context_->dartMethodPtr()->flushUICommand(context_->contextId()); + } +#endif +} + void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr) { UICommandItem item{id, static_cast(type), nativePtr}; addCommand(item); @@ -37,12 +46,17 @@ void UICommandBuffer::addCommand(int32_t id, void UICommandBuffer::addCommand(const UICommandItem& item) { if (size_ >= MAXIMUM_UI_COMMAND_SIZE) { - context_->FlushUICommand(); + if (UNLIKELY(isDartHotRestart())) { + clear(); + } else { + context_->FlushUICommand(); + } assert(size_ == 0); } #if FLUTTER_BACKEND - if (!update_batched_ && context_->IsValid() && context_->dartMethodPtr()->requestBatchUpdate != nullptr) { + if (UNLIKELY(!update_batched_ && context_->IsContextValid() && + context_->dartMethodPtr()->requestBatchUpdate != nullptr)) { context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); update_batched_ = true; } @@ -70,6 +84,7 @@ void UICommandBuffer::clear() { delete[] reinterpret_cast(buffer_[i].string_02); } size_ = 0; + memset(buffer_, 0, sizeof(buffer_)); update_batched_ = false; } diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index f21008edcb..8c03c70735 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -7,7 +7,6 @@ #define BRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ #include -#include #include "bindings/qjs/native_string_utils.h" #include "native_value.h" @@ -34,7 +33,7 @@ enum class UICommand { kCreatePerformance, }; -#define MAXIMUM_UI_COMMAND_SIZE 2056 +#define MAXIMUM_UI_COMMAND_SIZE 2048 struct UICommandItem { UICommandItem() = default; @@ -54,8 +53,8 @@ struct UICommandItem { nativePtr(reinterpret_cast(nativePtr)){}; UICommandItem(int32_t id, int32_t type, void* nativePtr) : type(type), id(id), nativePtr(reinterpret_cast(nativePtr)){}; - int32_t type; - int32_t id; + int32_t type{0}; + int32_t id{0}; int32_t args_01_length{0}; int32_t args_02_length{0}; int64_t string_01{0}; @@ -63,10 +62,13 @@ struct UICommandItem { int64_t nativePtr{0}; }; +bool isDartHotRestart(); + class UICommandBuffer { public: UICommandBuffer() = delete; explicit UICommandBuffer(ExecutingContext* context); + ~UICommandBuffer(); void addCommand(int32_t id, UICommand type, void* nativePtr); void addCommand(int32_t id, UICommand type, @@ -85,7 +87,7 @@ class UICommandBuffer { ExecutingContext* context_{nullptr}; UICommandItem buffer_[MAXIMUM_UI_COMMAND_SIZE]; bool update_batched_{false}; - size_t size_{0}; + int64_t size_{0}; }; } // namespace webf diff --git a/bridge/include/webf_bridge.h b/bridge/include/webf_bridge.h index b719fe2cfe..dd355c5a7d 100644 --- a/bridge/include/webf_bridge.h +++ b/bridge/include/webf_bridge.h @@ -12,6 +12,7 @@ #define WEBF_EXPORT __attribute__((__visibility__("default"))) typedef struct NativeString NativeString; +typedef struct NativeValue NativeValue; typedef struct NativeScreen NativeScreen; typedef struct NativeByteCode NativeByteCode; @@ -28,11 +29,11 @@ typedef void (*Task)(void*); typedef void (*ConsoleMessageHandler)(void* ctx, const std::string& message, int logLevel); WEBF_EXPORT_C -void initJSPagePool(int poolSize); +void initJSPagePool(int poolSize, uint64_t* dart_methods, int32_t dart_methods_len); WEBF_EXPORT_C void disposePage(int32_t contextId); WEBF_EXPORT_C -int32_t allocateNewPage(int32_t targetContextId); +int32_t allocateNewPage(int32_t targetContextId, uint64_t* dart_methods, int32_t dart_methods_len); WEBF_EXPORT_C void* getPage(int32_t contextId); bool checkPage(int32_t contextId); @@ -44,15 +45,13 @@ void evaluateQuickjsByteCode(int32_t contextId, uint8_t* bytes, int32_t byteLen) WEBF_EXPORT_C void parseHTML(int32_t contextId, const char* code, int32_t length); WEBF_EXPORT_C -void reloadJsContext(int32_t contextId); +void reloadJsContext(int32_t contextId, uint64_t* dart_methods, int32_t dart_methods_len); WEBF_EXPORT_C -void invokeModuleEvent(int32_t contextId, - NativeString* module, - const char* eventType, - void* event, - NativeString* extra); -WEBF_EXPORT_C -void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length); +NativeValue* invokeModuleEvent(int32_t contextId, + NativeString* module, + const char* eventType, + void* event, + NativeValue* extra); WEBF_EXPORT_C WebFInfo* getWebFInfo(); WEBF_EXPORT_C diff --git a/bridge/polyfill/src/bridge.ts b/bridge/polyfill/src/bridge.ts index 689ede23ef..7bf669719c 100644 --- a/bridge/polyfill/src/bridge.ts +++ b/bridge/polyfill/src/bridge.ts @@ -3,12 +3,18 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -declare const __webf_invoke_module__: (module: string, method: string, params?: Object | null, fn?: (err: Error, data: any) => void) => string; +declare const __webf_invoke_module__: (module: string, method: string, params?: any | null, fn?: (err: Error, data: any) => any) => any; export const webfInvokeModule = __webf_invoke_module__; -declare const __webf_add_module_listener__: (fn: (moduleName: string, event: Event, extra: string) => void) => void; +declare const __webf_add_module_listener__: (moduleName: string, fn: (event: Event, extra: any) => any) => void; export const addWebfModuleListener = __webf_add_module_listener__; +declare const __webf_clear_module_listener__: () => void; +export const clearWebfModuleListener = __webf_clear_module_listener__; + +declare const __webf_remove_module_listener__: (name: string) => void; +export const removeWebfModuleListener = __webf_remove_module_listener__; + declare const __webf_location_reload__: () => void; export const webfLocationReload = __webf_location_reload__; diff --git a/bridge/polyfill/src/method-channel.ts b/bridge/polyfill/src/method-channel.ts index 179623c855..0c48719b78 100644 --- a/bridge/polyfill/src/method-channel.ts +++ b/bridge/polyfill/src/method-channel.ts @@ -5,27 +5,23 @@ import { webfInvokeModule } from './bridge'; -type MethodCallHandler = (method: string, args: any[]) => void; +type MethodCallHandler = (args: any[]) => void; -let methodCallHandlers: MethodCallHandler[] = []; +let methodCallHandlers: {[key: string]: MethodCallHandler} = {}; // Like flutter platform channels export const methodChannel = { - setMethodCallHandler(handler: MethodCallHandler) { - console.warn('webf.methodChannel.setMethodCallHandler is a Deprecated API, use webf.methodChannel.addMethodCallHandler instead.'); - methodChannel.addMethodCallHandler(handler); - }, - addMethodCallHandler(handler: MethodCallHandler) { - methodCallHandlers.push(handler); - }, - removeMethodCallHandler(handler: MethodCallHandler) { - let index = methodCallHandlers.indexOf(handler); - if (index != -1) { - methodCallHandlers.splice(index, 1); + addMethodCallHandler(method: string, handler: MethodCallHandler) { + if (typeof handler !== 'function') { + throw new Error('webf.addMethodCallHandler: handler should be an function.'); } + methodCallHandlers[method] = handler; + }, + removeMethodCallHandler(method: string) { + delete methodCallHandlers[method]; }, clearMethodCallHandler() { - methodCallHandlers.length = 0; + methodCallHandlers = {}; }, invokeMethod(method: string, ...args: any[]): Promise { return new Promise((resolve, reject) => { @@ -37,10 +33,10 @@ export const methodChannel = { }, }; -export function triggerMethodCallHandler(method: string, args: any) { - if (methodCallHandlers.length > 0) { - for (let handler of methodCallHandlers) { - handler(method, args); - } +export function triggerMethodCallHandler(method: string, args: any[]) { + if (!methodCallHandlers.hasOwnProperty(method)) { + return null; } + + return methodCallHandlers[method](args); } diff --git a/bridge/polyfill/src/webf.ts b/bridge/polyfill/src/webf.ts index 20d47d6e66..b6dc416c83 100644 --- a/bridge/polyfill/src/webf.ts +++ b/bridge/polyfill/src/webf.ts @@ -3,29 +3,17 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import { addWebfModuleListener, webfInvokeModule } from './bridge'; +import { addWebfModuleListener, webfInvokeModule, clearWebfModuleListener, removeWebfModuleListener } from './bridge'; import { methodChannel, triggerMethodCallHandler } from './method-channel'; import { dispatchConnectivityChangeEvent } from "./connection"; -function webfModuleListener(moduleName: string, event: Event, data: any) { - switch (moduleName) { - case 'Connection': { - dispatchConnectivityChangeEvent(event); - break; - } - case 'MethodChannel': { - const method = data[0]; - const args = data[1]; - triggerMethodCallHandler(method, args); - break; - } - } -} - -addWebfModuleListener(webfModuleListener); +addWebfModuleListener('Connection', (event, data) => dispatchConnectivityChangeEvent(event)); +addWebfModuleListener('MethodChannel', (event, data) => triggerMethodCallHandler(data[0], data[1])); export const webf = { methodChannel, invokeModule: webfInvokeModule, - addWebfModuleListener: addWebfModuleListener + addWebfModuleListener: addWebfModuleListener, + clearWebfModuleListener: clearWebfModuleListener, + removeWebfModuleListener: removeWebfModuleListener }; diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index ba6bcb3607..72400e3835 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -45,7 +45,7 @@ function genCodeFromTypeDefine() { // Analyze all files first. for (let i = 0; i < blobs.length; i ++) { let b = blobs[i]; - analyzer(b); + analyzer(b, definedPropertyCollector); } for (let i = 0; i < blobs.length; i ++) { @@ -82,7 +82,6 @@ function genCodeFromJSONData() { return new JSONTemplate(path.join(path.join(__dirname, '../templates/json_templates'), template), filename); }); - let names_needs_install = new Set(); for (let i = 0; i < blobs.length; i ++) { let blob = blobs[i]; blob.json.metadata.templates.forEach((targetTemplate) => { @@ -97,6 +96,19 @@ function genCodeFromJSONData() { depsBlob[filename] = new JSONBlob(path.join(cwdDir, depPath), filename).json; }); } + + // Inject allDefinedProperties set into the definedProperties source. + if (targetTemplate.filename === 'defined_properties') { + blob.json.data = Array.from(definedPropertyCollector.properties); + } + + if (targetTemplate.filename === 'defined_properties_initializer') { + blob.json.data = { + filenames: Array.from(definedPropertyCollector.files), + interfaces: Array.from(definedPropertyCollector.interfaces) + }; + } + let targetTemplateHeaderData = templates.find(t => t.filename === targetTemplate.template + '.h'); let targetTemplateBodyData = templates.find(t => t.filename === targetTemplate.template + '.cc'); blob.filename = targetTemplate.filename; @@ -117,5 +129,14 @@ function genCodeFromJSONData() { result.source && fs.writeFileSync(genFilePath + '.cc', result.source); } +class DefinedPropertyCollector { + properties = new Set(); + files = new Set(); + interfaces = new Set(); +} + +let definedPropertyCollector = new DefinedPropertyCollector(); +let names_needs_install = new Set(); + genCodeFromTypeDefine(); genCodeFromJSONData(); diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 194a22b3b1..0e599b1436 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -11,12 +11,17 @@ import { ParameterMode, PropsDeclaration, } from './declaration'; -import {generatorSource} from './generator'; -export function analyzer(blob: IDLBlob) { +interface DefinedPropertyCollector { + properties: Set; + files: Set; + interfaces: Set; +} + +export function analyzer(blob: IDLBlob, definedPropertyCollector: DefinedPropertyCollector) { let code = blob.raw; const sourceFile = ts.createSourceFile(blob.source, blob.raw, ScriptTarget.ES2020); - blob.objects = sourceFile.statements.map(statement => walkProgram(statement)).filter(o => { + blob.objects = sourceFile.statements.map(statement => walkProgram(blob, statement, definedPropertyCollector)).filter(o => { return o instanceof ClassObject || o instanceof FunctionObject; }) as (FunctionObject | ClassObject)[]; } @@ -151,7 +156,7 @@ function isParamsReadOnly(m: ts.PropertySignature): boolean { return m.modifiers.some(k => k.kind === ts.SyntaxKind.ReadonlyKeyword); } -function walkProgram(statement: ts.Statement) { +function walkProgram(blob: IDLBlob, statement: ts.Statement, definedPropertyCollector: DefinedPropertyCollector) { switch(statement.kind) { case ts.SyntaxKind.InterfaceDeclaration: { let interfaceName = getInterfaceName(statement) as string; @@ -179,6 +184,11 @@ function walkProgram(statement: ts.Statement) { } } + if (obj.kind === ClassObjectKind.interface) { + definedPropertyCollector.interfaces.add('QJS' + interfaceName); + definedPropertyCollector.files.add(blob.filename); + } + s.members.forEach(member => { switch(member.kind) { case ts.SyntaxKind.PropertySignature: { @@ -187,6 +197,10 @@ function walkProgram(statement: ts.Statement) { prop.name = getPropName(m.name); prop.readonly = isParamsReadOnly(m); + if (obj.kind === ClassObjectKind.interface) { + definedPropertyCollector.properties.add(prop.name); + } + let propKind = m.type; if (propKind) { let mode = new ParameterMode(); diff --git a/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl index 9c2c2219e4..716b6c8404 100644 --- a/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl +++ b/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl @@ -20,6 +20,7 @@ #include "core/dom/comment.h" #include "core/input/touch_list.h" #include "core/html/html_all_collection.h" +#include "defined_properties.h" namespace webf { 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 3c6ddd7631..85d96d0a1c 100644 --- a/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl @@ -95,6 +95,31 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob <% } %> +static thread_local AttributeMap* internal_properties = nullptr; + +void QJS<%= className %>::InitAttributeMap() { + internal_properties = new AttributeMap(); + + for(int i = 0; i < <%= object.props.length %>; i ++) { + <% object.props.forEach(prop => { %> + internal_properties->emplace(std::make_pair(defined_properties::k<%= prop.name %>, true)); + <% }) %> + } +} + +void QJS<%= className %>::DisposeAttributeMap() { + delete internal_properties; +} + +AttributeMap* QJS<%= className %>::definedAttributeMap() { + assert(internal_properties != nullptr); + return internal_properties; +} + +bool QJS<%= className %>::IsAttributeDefinedInternal(const AtomicString& key) { + return definedAttributeMap()->count(key) > 0; +} + <% _.forEach(filtedMethods, function(method, index) { %> <% if (overloadMethods[method.name] && overloadMethods[method.name].length > 1) { %> diff --git a/bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl b/bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl index 8106a8363e..349d61a50f 100644 --- a/bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl +++ b/bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl @@ -33,9 +33,15 @@ Native<%= parentClassName %> native_event; #endif <% } %> +using AttributeMap = std::unordered_map; + class QJS<%= className %> : public QJSInterfaceBridge, <%= className%>> { public: static void Install(ExecutingContext* context); + static void InitAttributeMap(); + static void DisposeAttributeMap(); + static AttributeMap* definedAttributeMap(); + static bool IsAttributeDefinedInternal(const AtomicString& key); static WrapperTypeInfo* GetWrapperTypeInfo() { return const_cast(&wrapper_type_info_); } diff --git a/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.cc.tpl new file mode 100644 index 0000000000..6d8d1c740a --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.cc.tpl @@ -0,0 +1,27 @@ +// Generated from template: +// code_generator/src/json/templates/defined_properties_initializer.cc.tpl +// and input files: +// <%= template_path %> + +#include "defined_properties_initializer.h" + +<% data.filenames.forEach(filename => { %> +#include "<%= filename %>.h" +<% }) %> + +namespace webf { + +void DefinedPropertiesInitializer::Init() { + <% data.interfaces.forEach(interfaceName => { %> + <%= interfaceName %>::InitAttributeMap(); + <% }) %> +} + +void DefinedPropertiesInitializer::Dispose() { + <% data.interfaces.forEach(interfaceName => { %> + <%= interfaceName %>::DisposeAttributeMap(); + <% }) %> +} + + +} \ No newline at end of file diff --git a/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.h.tpl b/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.h.tpl new file mode 100644 index 0000000000..77da466fa5 --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.h.tpl @@ -0,0 +1,22 @@ +/* +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ + +#ifndef BRIDGE_DEFINED_PROPERTIES_INTIALIZER_H_ +#define BRIDGE_DEFINED_PROPERTIES_INTIALIZER_H_ + +#include "bindings/qjs/atomic_string.h" + +namespace webf { + +class DefinedPropertiesInitializer { + public: + static void Init(); + static void Dispose(); +}; + + +} // namespace webf + +#endif // BRIDGE_DEFINED_PROPERTIES_INTIALIZER_H_ diff --git a/bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl index 5f1d4fcf6b..01ffa2f70d 100644 --- a/bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl +++ b/bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl @@ -31,7 +31,7 @@ using HTMLConstructorFunction = HTMLElement* (*)(Document&); using HTMLFunctionMap = std::unordered_map; -static HTMLFunctionMap* g_html_constructors = nullptr; +static thread_local HTMLFunctionMap* g_html_constructors = nullptr; struct CreateHTMLFunctionMapData { const AtomicString& tag; diff --git a/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl index f2a881a056..ebbce63c03 100644 --- a/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl +++ b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl @@ -12,6 +12,7 @@ #include #include "event_type_names.h" #include "bindings/qjs/cppgc/garbage_collected.h" +#include "core/dom/events/custom_event.h" <% _.forEach(data, (item, index) => { %> <% if (_.isString(item)) { %> @@ -28,7 +29,7 @@ using EventConstructorFunction = Event* (*)(ExecutingContext* context, const Ato using EventMap = std::unordered_map; -static EventMap* g_event_constructors = nullptr; +static thread_local EventMap* g_event_constructors = nullptr; struct CreateEventFunctionMapData { const AtomicString& tag; @@ -84,6 +85,11 @@ static void CreateEventFunctionMap() { Event* EventFactory::Create(ExecutingContext* context, const AtomicString& type, RawEvent* raw_event) { if (!g_event_constructors) CreateEventFunctionMap(); + + if (raw_event != nullptr && raw_event->is_custom_event) { + return MakeGarbageCollected(context, type, toNativeEvent(raw_event)); + } + auto it = g_event_constructors->find(type); if (it == g_event_constructors->end()) { if (raw_event == nullptr) { diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index 6df6967ca6..972acc4c39 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -33,6 +33,7 @@ list(APPEND WEBF_UNIT_TEST_SOURCEURCE ./core/frame/window_test.cc ./core/css/legacy/css_style_declaration_test.cc ./core/html/html_element_test.cc + ./core/html/custom/widget_element_test.cc ./core/timing/performance_test.cc ) diff --git a/bridge/test/webf_test_context.cc b/bridge/test/webf_test_context.cc index 076fd6302a..f5fb478a6a 100644 --- a/bridge/test/webf_test_context.cc +++ b/bridge/test/webf_test_context.cc @@ -8,6 +8,7 @@ #include "core/dom/document.h" #include "core/fileapi/blob.h" #include "core/html/html_body_element.h" +#include "core/html/html_html_element.h" #include "core/html/parser/html_parser.h" #include "qjs_blob.h" #include "testframework.h" @@ -195,8 +196,7 @@ static JSValue parseHTML(JSContext* ctx, JSValueConst this_val, int argc, JSValu if (argc == 1) { std::string strHTML = AtomicString(ctx, argv[0]).ToStdString(); - auto* body = context->document()->body(); - HTMLParser::parseHTML(strHTML, body); + HTMLParser::parseHTML(strHTML, context->document()->documentElement()); } return JS_NULL; @@ -285,13 +285,13 @@ bool WebFTestContext::evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return false; return context_->EvaluateJavaScript(code, codeLength, sourceURL, startLine); } bool WebFTestContext::parseTestHTML(const uint16_t* code, size_t codeLength) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return false; std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), codeLength)); return page_->parseHTML(utf8Code.c_str(), utf8Code.length()); diff --git a/bridge/test/webf_test_env.cc b/bridge/test/webf_test_env.cc index fb50e8bbbf..987c82d668 100644 --- a/bridge/test/webf_test_env.cc +++ b/bridge/test/webf_test_env.cc @@ -11,6 +11,7 @@ #include "core/frame/dom_timer.h" #include "core/page.h" #include "foundation/native_string.h" +#include "foundation/native_value_converter.h" #include "webf_bridge_test.h" #include "webf_test_env.h" @@ -61,12 +62,12 @@ static void unlink_callback(JSThreadState* ts, JSFrameCallback* th) { ts->os_frameCallbacks.erase(th->callbackId); } -NativeString* TEST_invokeModule(void* callbackContext, - int32_t contextId, - NativeString* moduleName, - NativeString* method, - NativeString* params, - AsyncModuleCallback callback) { +NativeValue* TEST_invokeModule(void* callbackContext, + int32_t contextId, + NativeString* moduleName, + NativeString* method, + NativeString* params, + AsyncModuleCallback callback) { std::string module = nativeStringToStdString(moduleName); if (module == "throwError") { @@ -74,10 +75,14 @@ NativeString* TEST_invokeModule(void* callbackContext, } if (module == "MethodChannel") { - callback(callbackContext, contextId, nullptr, stringToNativeString("{\"result\": 1234}").release()); + NativeValue data = Native_NewCString("{\"result\": 1234}"); + callback(callbackContext, contextId, nullptr, &data); } - return stringToNativeString(module).release(); + auto* result = static_cast(malloc(sizeof(NativeValue))); + NativeValue tmp = Native_NewCString(module); + memcpy(result, &tmp, sizeof(NativeValue)); + return result; }; void TEST_requestBatchUpdate(int32_t contextId){}; @@ -174,7 +179,9 @@ void TEST_toBlob(void* ptr, blobCallback(ptr, contextId, nullptr, bytes, 5); } -void TEST_flushUICommand() {} +void TEST_flushUICommand(int32_t contextId) { + clearUICommandItems(contextId); +} void TEST_onJsLog(int32_t contextId, int32_t level, const char*) {} @@ -189,18 +196,17 @@ static int32_t inited{false}; std::unique_ptr TEST_init(OnJSError onJsError) { uint32_t contextId; + auto mockedDartMethods = TEST_getMockDartMethods(onJsError); if (inited) { - contextId = allocateNewPage(-1); + contextId = allocateNewPage(-1, mockedDartMethods.data(), mockedDartMethods.size()); } else { contextId = 0; } - std::call_once(testInitOnceFlag, []() { - initJSPagePool(1024 * 1024); + std::call_once(testInitOnceFlag, [&mockedDartMethods]() { + initJSPagePool(1024 * 1024, mockedDartMethods.data(), mockedDartMethods.size()); inited = true; }); - TEST_mockDartMethods(contextId, onJsError); - initTestFramework(contextId); TEST_mockTestEnvDartMethods(contextId, onJsError); auto* page = static_cast(getPage(contextId)); @@ -216,9 +222,8 @@ std::unique_ptr TEST_init() { } std::unique_ptr TEST_allocateNewPage(OnJSError onJsError) { - uint32_t newContextId = allocateNewPage(-1); - - TEST_mockDartMethods(newContextId, onJsError); + auto mockedDartMethods = TEST_getMockDartMethods(onJsError); + uint32_t newContextId = allocateNewPage(-1, mockedDartMethods.data(), mockedDartMethods.size()); initTestFramework(newContextId); return std::unique_ptr(static_cast(getPage(newContextId))); @@ -297,7 +302,7 @@ void TEST_simulatePointer(MousePointer*, int32_t length, int32_t pointer) {} void TEST_simulateInputText(NativeString* nativeString) {} -void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { +std::vector TEST_getMockDartMethods(OnJSError onJSError) { std::vector mockMethods{ reinterpret_cast(TEST_invokeModule), reinterpret_cast(TEST_requestBatchUpdate), @@ -319,7 +324,7 @@ void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { mockMethods.emplace_back(reinterpret_cast(onJSError)); mockMethods.emplace_back(reinterpret_cast(TEST_onJsLog)); - registerDartMethods(contextId, mockMethods.data(), mockMethods.size()); + return mockMethods; } void TEST_mockTestEnvDartMethods(int32_t contextId, OnJSError onJSError) { diff --git a/bridge/test/webf_test_env.h b/bridge/test/webf_test_env.h index 15fbe34576..96262e5ecf 100644 --- a/bridge/test/webf_test_env.h +++ b/bridge/test/webf_test_env.h @@ -7,6 +7,7 @@ #define BRIDGE_TEST_WEBF_TEST_ENV_H_ #include +#include "bindings/qjs/cppgc/mutation_scope.h" #include "core/dart_methods.h" #include "core/executing_context.h" #include "core/page.h" @@ -28,7 +29,7 @@ std::unique_ptr TEST_init(OnJSError onJsError); std::unique_ptr TEST_init(); std::unique_ptr TEST_allocateNewPage(OnJSError onJsError); void TEST_runLoop(ExecutingContext* context); -void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError); +std::vector TEST_getMockDartMethods(OnJSError onJSError); void TEST_mockTestEnvDartMethods(int32_t contextId, OnJSError onJSError); void TEST_registerEventTargetDisposedCallback(int32_t context_unique_id, TEST_OnEventTargetDisposed callback); std::shared_ptr TEST_getEnv(int32_t context_unique_id); diff --git a/bridge/webf_bridge.cc b/bridge/webf_bridge.cc index 971ddd14f1..1736aeae5c 100644 --- a/bridge/webf_bridge.cc +++ b/bridge/webf_bridge.cc @@ -40,6 +40,7 @@ // this is not thread safe std::atomic inited{false}; +std::atomic is_dart_hot_restart{false}; std::atomic poolIndex{0}; int maxPoolSize = 0; @@ -64,17 +65,25 @@ int32_t searchForAvailableContextId() { } // namespace -void initJSPagePool(int poolSize) { +namespace webf { +bool isDartHotRestart() { + return is_dart_hot_restart; +} +} // namespace webf + +void initJSPagePool(int poolSize, uint64_t* dart_methods, int32_t dart_methods_len) { // When dart hot restarted, should dispose previous bridge and clear task message queue. if (inited) { + is_dart_hot_restart = true; disposeAllPages(); + is_dart_hot_restart = false; }; webf::WebFPage::pageContextPool = new webf::WebFPage*[poolSize]; for (int i = 1; i < poolSize; i++) { webf::WebFPage::pageContextPool[i] = nullptr; } - webf::WebFPage::pageContextPool[0] = new webf::WebFPage(0, nullptr); + webf::WebFPage::pageContextPool[0] = new webf::WebFPage(0, nullptr, dart_methods, dart_methods_len); inited = true; maxPoolSize = poolSize; } @@ -89,7 +98,7 @@ void disposePage(int32_t contextId) { webf::WebFPage::pageContextPool[contextId] = nullptr; } -int32_t allocateNewPage(int32_t targetContextId) { +int32_t allocateNewPage(int32_t targetContextId, uint64_t* dart_methods, int32_t dart_methods_len) { if (targetContextId == -1) { targetContextId = ++poolIndex; } @@ -102,7 +111,7 @@ int32_t allocateNewPage(int32_t targetContextId) { (std::string("can not Allocate page at index") + std::to_string(targetContextId) + std::string(": page have already exist.")) .c_str()); - auto* page = new webf::WebFPage(targetContextId, nullptr); + auto* page = new webf::WebFPage(targetContextId, nullptr, dart_methods, dart_methods_len); webf::WebFPage::pageContextPool[targetContextId] = page; return targetContextId; } @@ -142,30 +151,25 @@ void parseHTML(int32_t contextId, const char* code, int32_t length) { context->parseHTML(code, length); } -void reloadJsContext(int32_t contextId) { +void reloadJsContext(int32_t contextId, uint64_t* dart_methods, int32_t dart_methods_len) { assert(checkPage(contextId) && "reloadJSContext: contextId is not valid"); auto bridgePtr = getPage(contextId); auto context = static_cast(bridgePtr); - auto newContext = new webf::WebFPage(contextId, nullptr); + auto newContext = new webf::WebFPage(contextId, nullptr, dart_methods, dart_methods_len); delete context; webf::WebFPage::pageContextPool[contextId] = newContext; } -void invokeModuleEvent(int32_t contextId, - NativeString* moduleName, - const char* eventType, - void* event, - NativeString* extra) { +NativeValue* invokeModuleEvent(int32_t contextId, + NativeString* moduleName, + const char* eventType, + void* event, + NativeValue* extra) { assert(checkPage(contextId) && "invokeEventListener: contextId is not valid"); auto context = static_cast(getPage(contextId)); - context->invokeModuleEvent(reinterpret_cast(moduleName), eventType, event, - reinterpret_cast(extra)); -} - -void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length) { - assert(checkPage(contextId) && "registerDartMethods: contextId is not valid"); - auto context = static_cast(getPage(contextId)); - context->registerDartMethods(methodBytes, length); + auto* result = context->invokeModuleEvent(reinterpret_cast(moduleName), eventType, event, + reinterpret_cast(extra)); + return reinterpret_cast(result); } static WebFInfo* webfInfo{nullptr}; diff --git a/integration_tests/lib/main.dart b/integration_tests/lib/main.dart index b5e8508a51..843527cdbc 100644 --- a/integration_tests/lib/main.dart +++ b/integration_tests/lib/main.dart @@ -12,6 +12,7 @@ import 'package:webf/dom.dart'; import 'package:webf/gesture.dart'; import 'package:webf/webf.dart'; +import 'test_module.dart'; import 'bridge/from_native.dart'; import 'bridge/test_input.dart'; import 'bridge/to_native.dart'; @@ -30,6 +31,8 @@ void main() async { WebFDynamicLibrary.libName = 'libwebf_test'; defineWebFCustomElements(); + ModuleManager.defineModule((moduleManager) => DemoModule(moduleManager)); + // FIXME: This is a workaround for testcases. ParagraphElement.defaultStyle = {DISPLAY: BLOCK}; @@ -49,8 +52,8 @@ void main() async { final File spec = File(path.join(testDirectory, specTarget)); WebFJavaScriptChannel javaScriptChannel = WebFJavaScriptChannel(); javaScriptChannel.onMethodCall = (String method, dynamic arguments) async { - javaScriptChannel.invokeMethod(method, arguments); - return 'method: ' + method; + dynamic returnedValue = await javaScriptChannel.invokeMethod(method, arguments); + return 'method: $method, return_type: ${returnedValue.runtimeType.toString()}, return_value: ${returnedValue.toString()}'; }; // This is a virtual location for test program to test [Location] functionality. diff --git a/integration_tests/lib/test_module.dart b/integration_tests/lib/test_module.dart new file mode 100644 index 0000000000..01db3c088d --- /dev/null +++ b/integration_tests/lib/test_module.dart @@ -0,0 +1,62 @@ +import 'dart:async'; +import 'package:webf/webf.dart'; +import 'package:webf/dom.dart'; + +class DemoModule extends BaseModule { + DemoModule(ModuleManager? moduleManager) : super(moduleManager); + + @override + String get name => "Demo"; + + @override + dynamic invoke(String method, params, InvokeModuleCallback callback) { + switch (method) { + case 'noParams': + assert(params == null); + return true; + case 'callInt': + assert(params is int); + return params + params; + case 'callDouble': + assert(params is double); + return (params as double) + params; + case 'callString': + assert(params == 'helloworld'); + return (params as String).toUpperCase(); + case 'callArray': + assert(params is List); + return (params as List).reduce((e, i) => e + i); + case 'callNull': + return null; + case 'callObject': + assert(params is Map); + return params['value']; + case 'callAsyncFn': + Timer(Duration(milliseconds: 100), () async { + dynamic returnValue = await callback(data: [ + 1, + '2', + null, + 4.0, + {'value': 1} + ]); + assert(returnValue == 'success'); + }); + return params; + case 'callAsyncFnFail': + Timer(Duration(milliseconds: 100), () async { + dynamic returnValue = await callback(error: 'Must to fail'); + assert(returnValue == 'fail'); + }); + return null; + case 'callToDispatchEvent': + CustomEvent customEvent = CustomEvent('click', detail: 'helloworld'); + dynamic result = + dispatchEvent(event: customEvent, data: [1, 2, 3, 4, 5]); + assert(result == 'success'); + } + } + + @override + void dispose() {} +} diff --git a/integration_tests/runtime/kraken.d.ts b/integration_tests/runtime/kraken.d.ts index 54e08e7034..971912f610 100644 --- a/integration_tests/runtime/kraken.d.ts +++ b/integration_tests/runtime/kraken.d.ts @@ -1,14 +1,17 @@ /* * Copyright (C) 2022-present The Kraken authors. All rights reserved. */ -type MethodHandler = (method: string, args: any[]) => void; +type MethodCallHandler = (args: any) => any; interface MethodChannel { - addMethodCallHandler(handler: MethodHandler): void; - removeMethodCallHandler(handler: MethodHandler): void; + addMethodCallHandler(method: string, handler: MethodCallHandler): void; + removeMethodCallHandler(method: string): void; + clearMethodCallHandler(): void; invokeMethod(method: string, ...args: any[]): Promise } interface WebF { + invokeModule: (module: string, method: string, params?: any | null, fn?: (err: Error, data: any) => any) => any; + addWebfModuleListener: (moduleName: string, fn: (event: Event, extra: any) => any) => void; methodChannel: MethodChannel; } diff --git a/integration_tests/scripts/core_integration_starter.js b/integration_tests/scripts/core_integration_starter.js index 64eeb8ff14..2412767e98 100644 --- a/integration_tests/scripts/core_integration_starter.js +++ b/integration_tests/scripts/core_integration_starter.js @@ -38,13 +38,18 @@ function startIntegrationTest() { }); tester.on('close', (code) => { + console.log(code); process.exit(code); }); tester.on('error', (error) => { - console.error(error); + console.error('integration failed', error); process.exit(1); }); tester.on('exit', (code, signal) => { + if (signal) { + console.log('Process exit with ' + signal); + process.exit(1); + } if (code != 0) { process.exit(1); } diff --git a/integration_tests/scripts/html_loader.js b/integration_tests/scripts/html_loader.js index 0ae1c7e683..3c18187b24 100644 --- a/integration_tests/scripts/html_loader.js +++ b/integration_tests/scripts/html_loader.js @@ -41,10 +41,14 @@ const loader = function(source) { // Set attr of HTML can let the case use fit. For example: xxx . let isFit = false; + let isXit = false; root.childNodes && root.childNodes.forEach(ele => { if (ele.rawAttrs && ele.rawAttrs.indexOf('fit') >= 0) { isFit = true; } + if (ele.rawAttrs && ele.rawAttrs.indexOf('xit') >= 0) { + isXit = true; + } }) const htmlString = root.toString().replace(/['\n]/g, function(c){ @@ -57,7 +61,7 @@ const loader = function(source) { const html_parse = () => __webf_parse_html__('${htmlString}'); var index = 0; const snapshotAction = async () => { await snapshot(null, '${snapshotFilepath}', ${scripts.length === 0 ? 'null' : 'index.toString()'}); index++; }; - ${isFit ? 'fit' : 'it'}("should work", async (done) => {\ + ${isFit ? 'fit' : isXit ? 'xit' : 'it'}("should work", async (done) => {\ html_parse();\ requestAnimationFrame(async () => { ${scripts.length === 0 ? `await snapshotAction();` : scripts.join('\n')} diff --git a/integration_tests/snapshots/css/css-color/hsl-001.html.65645d7c1.png b/integration_tests/snapshots/css/css-color/hsl-001.html.65645d7c1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-001.html.65645d7c1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-002.html.797a2f5a1.png b/integration_tests/snapshots/css/css-color/hsl-002.html.797a2f5a1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-002.html.797a2f5a1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-003.html.ac8782251.png b/integration_tests/snapshots/css/css-color/hsl-003.html.ac8782251.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-003.html.ac8782251.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-004.html.972de9c51.png b/integration_tests/snapshots/css/css-color/hsl-004.html.972de9c51.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-004.html.972de9c51.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-005.html.b1402b061.png b/integration_tests/snapshots/css/css-color/hsl-005.html.b1402b061.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-005.html.b1402b061.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-006.html.cc1c51e11.png b/integration_tests/snapshots/css/css-color/hsl-006.html.cc1c51e11.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-006.html.cc1c51e11.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-007.html.8d366c2f1.png b/integration_tests/snapshots/css/css-color/hsl-007.html.8d366c2f1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-007.html.8d366c2f1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-008.html.0ef86b9b1.png b/integration_tests/snapshots/css/css-color/hsl-008.html.0ef86b9b1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-008.html.0ef86b9b1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-001.html.36c76bb61.png b/integration_tests/snapshots/css/css-color/hsla-001.html.36c76bb61.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-001.html.36c76bb61.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-002.html.0718b4031.png b/integration_tests/snapshots/css/css-color/hsla-002.html.0718b4031.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-002.html.0718b4031.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-003.html.6088464b1.png b/integration_tests/snapshots/css/css-color/hsla-003.html.6088464b1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-003.html.6088464b1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-004.html.84d8420e1.png b/integration_tests/snapshots/css/css-color/hsla-004.html.84d8420e1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-004.html.84d8420e1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-005.html.d3a946911.png b/integration_tests/snapshots/css/css-color/hsla-005.html.d3a946911.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-005.html.d3a946911.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-006.html.b259b3691.png b/integration_tests/snapshots/css/css-color/hsla-006.html.b259b3691.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-006.html.b259b3691.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-007.html.f58df36c1.png b/integration_tests/snapshots/css/css-color/hsla-007.html.f58df36c1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-007.html.f58df36c1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-008.html.be9b8de71.png b/integration_tests/snapshots/css/css-color/hsla-008.html.be9b8de71.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-008.html.be9b8de71.png differ diff --git a/integration_tests/spec_group.json b/integration_tests/spec_group.json index aeb69201e1..1d2a1d93f7 100644 --- a/integration_tests/spec_group.json +++ b/integration_tests/spec_group.json @@ -6,7 +6,7 @@ "specs/blob/**/*.{js,jsx,ts,tsx,html}", "specs/cookie/**/*.{js,jsx,ts,tsx,html}", "specs/fetch/**/*.{js,jsx,ts,tsx,html}", - "specs/method-channel/**/*.{js,jsx,ts,tsx,html}", + "specs/modules/**/*.{js,jsx,ts,tsx,html}", "specs/navigator/**/*.{js,jsx,ts,tsx,html}", "specs/performance/**/*.{js,jsx,ts,tsx,html}", "specs/timer/**/*.{js,jsx,ts,tsx,html}", diff --git a/integration_tests/specs/cookie/cookie.ts b/integration_tests/specs/cookie/cookie.ts index 8a05baedb9..1781fe1be9 100644 --- a/integration_tests/specs/cookie/cookie.ts +++ b/integration_tests/specs/cookie/cookie.ts @@ -1,8 +1,8 @@ describe('Cookie', () => { it('works with cookie getter and setter', async () => { + expect(document.cookie).toBe(''); document.cookie = "name=oeschger"; document.cookie = "favorite_food=tripe"; - document.body.appendChild(document.createTextNode(document.cookie)); - await snapshot(); + expect(document.cookie).toBe('name=oeschger; favorite_food=tripe'); }); }); diff --git a/integration_tests/specs/css/css-animations/animation-delay-001-manual.html b/integration_tests/specs/css/css-animations/animation-delay-001-manual.html index 378a6ca421..26a04dcc73 100644 --- a/integration_tests/specs/css/css-animations/animation-delay-001-manual.html +++ b/integration_tests/specs/css/css-animations/animation-delay-001-manual.html @@ -24,15 +24,15 @@ #test-negative-delay { animation-name: test-negative-delay; - animation-duration: 10s; - animation-delay: -5s; + animation-duration: 6s; + animation-delay: -3s; background-color: blue; } #ref-no-delay { animation-name: ref-no-delay; - animation-duration: 5s; + animation-duration: 3s; background-color: yellow; } @@ -65,8 +65,15 @@
Filler Text
\ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-delay-002-manual.html b/integration_tests/specs/css/css-animations/animation-delay-002-manual.html index 77821f8919..da4b530e14 100644 --- a/integration_tests/specs/css/css-animations/animation-delay-002-manual.html +++ b/integration_tests/specs/css/css-animations/animation-delay-002-manual.html @@ -30,11 +30,16 @@ which starts moving from right to left after about 5 seconds from the time the page is loaded.

-
Filler Text
+
Filler Text
\ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-delay-003-manual.html b/integration_tests/specs/css/css-animations/animation-delay-003-manual.html index f0672fbd92..6c024e0bc1 100644 --- a/integration_tests/specs/css/css-animations/animation-delay-003-manual.html +++ b/integration_tests/specs/css/css-animations/animation-delay-003-manual.html @@ -29,11 +29,14 @@ Test passes if there is a filled blue square with 'Filler Text', which starts moving from right to left as soon as the page loads.

-
Filler Text
+
Filler Text
diff --git a/integration_tests/specs/css/css-animations/animation-direction-001-manual.html b/integration_tests/specs/css/css-animations/animation-direction-001-manual.html index 1854712092..d3889eeef5 100644 --- a/integration_tests/specs/css/css-animations/animation-direction-001-manual.html +++ b/integration_tests/specs/css/css-animations/animation-direction-001-manual.html @@ -45,13 +45,14 @@ which starts moving from right to left on the page load, and then moves from left to right. This cycle gets repeated.

-
Filler Text
+
Filler Text
\ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-direction-002-manual.html b/integration_tests/specs/css/css-animations/animation-direction-002-manual.html index 52d356b987..e8ba43db9b 100644 --- a/integration_tests/specs/css/css-animations/animation-direction-002-manual.html +++ b/integration_tests/specs/css/css-animations/animation-direction-002-manual.html @@ -39,13 +39,16 @@ which starts moving from right to left, then back to right and moves from right to left again. This cycle gets repeated.

-
Filler Text
+
Filler Text
\ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-direction-003-manual.html b/integration_tests/specs/css/css-animations/animation-direction-003-manual.html index b736221d79..76039fc5ff 100644 --- a/integration_tests/specs/css/css-animations/animation-direction-003-manual.html +++ b/integration_tests/specs/css/css-animations/animation-direction-003-manual.html @@ -41,13 +41,18 @@ which starts moving from right to left on the page load, and then moves from left to right. This cycle gets repeated.

-
Filler Text
+
Filler Text
\ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-direction-004-manual.html b/integration_tests/specs/css/css-animations/animation-direction-004-manual.html index 8fdc331b0f..3e40c57c3d 100644 --- a/integration_tests/specs/css/css-animations/animation-direction-004-manual.html +++ b/integration_tests/specs/css/css-animations/animation-direction-004-manual.html @@ -39,13 +39,16 @@ which starts moving from left to right, then back to left again and moves from left to right. This cycle gets repeated.

-
Filler Text
+
Filler Text
\ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-duration-001-manual.html b/integration_tests/specs/css/css-animations/animation-duration-001-manual.html index b489be7fcf..a9cc85d7da 100644 --- a/integration_tests/specs/css/css-animations/animation-duration-001-manual.html +++ b/integration_tests/specs/css/css-animations/animation-duration-001-manual.html @@ -42,6 +42,6 @@ await sleep(2); div[0].style.animationDuration = "2s"; await sleep(1); - await snapshotAction(); + expect(div[0].offsetLeft < 50).toBe(true); \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-duration-002-manual.html b/integration_tests/specs/css/css-animations/animation-duration-002-manual.html index 28fe0bdd23..aa195cb7ad 100644 --- a/integration_tests/specs/css/css-animations/animation-duration-002-manual.html +++ b/integration_tests/specs/css/css-animations/animation-duration-002-manual.html @@ -34,11 +34,14 @@ Test passes if there is a filled blue square with 'Filler Text', which starts moving from right to left upon page load and lasts for a span of 10 seconds.

-
Filler Text
+
Filler Text
\ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-duration-003-manual.html b/integration_tests/specs/css/css-animations/animation-duration-003-manual.html index 50f98e6296..f372f9cba6 100644 --- a/integration_tests/specs/css/css-animations/animation-duration-003-manual.html +++ b/integration_tests/specs/css/css-animations/animation-duration-003-manual.html @@ -39,9 +39,10 @@ var div = document.getElementsByTagName("div"); div[0].style.animationDuration = "-2s"; await sleep(1); + expect(div[0].offsetLeft).toBe(0); div[0].style.animationDuration = "2s"; - await sleep(1); - await snapshotAction(); + await sleep(0.5); + expect(div[0].offsetLeft > 75).toBe(true); diff --git a/integration_tests/specs/css/css-animations/animation-duration-004-manual.html b/integration_tests/specs/css/css-animations/animation-duration-004-manual.html index 6cd65f547f..696a961136 100644 --- a/integration_tests/specs/css/css-animations/animation-duration-004-manual.html +++ b/integration_tests/specs/css/css-animations/animation-duration-004-manual.html @@ -1,5 +1,5 @@ - + CSS Animations Test: animation-duration - 0s @@ -36,12 +36,13 @@

Filler Text
diff --git a/integration_tests/specs/css/css-animations/animation-duration-005-manual.html b/integration_tests/specs/css/css-animations/animation-duration-005-manual.html index bc4fee621d..4007302ed5 100644 --- a/integration_tests/specs/css/css-animations/animation-duration-005-manual.html +++ b/integration_tests/specs/css/css-animations/animation-duration-005-manual.html @@ -1,5 +1,5 @@ - + CSS Animations Test: animation-duration - 0s, animation-fill-mode - forwards @@ -49,7 +49,9 @@ \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-duration-006-manual.html b/integration_tests/specs/css/css-animations/animation-duration-006-manual.html index fce51d5852..31029469c4 100644 --- a/integration_tests/specs/css/css-animations/animation-duration-006-manual.html +++ b/integration_tests/specs/css/css-animations/animation-duration-006-manual.html @@ -1,5 +1,5 @@ - + CSS Animations Test: animation-duration - 0s, animation-fill-mode - both diff --git a/integration_tests/specs/dom/events/event.ts b/integration_tests/specs/dom/events/event.ts index f8a5abd021..168244937d 100644 --- a/integration_tests/specs/dom/events/event.ts +++ b/integration_tests/specs/dom/events/event.ts @@ -471,4 +471,23 @@ describe('Event', () => { expect(ret).toEqual('1'); }); + it('should work with undefined addEventListener options', () => { + var el = createElement('div', { + style: { + width: '100px', + height: '100px', + background: 'red' + } + }); + let ret = ''; + function fn1() { + ret += '1'; + } + el.addEventListener('click', fn1, undefined); + el.addEventListener('scroll', fn1, undefined); + + el.click(); + + expect(ret).toEqual('1'); + }); }); diff --git a/integration_tests/specs/method-channel/method-channel.ts b/integration_tests/specs/method-channel/method-channel.ts deleted file mode 100644 index f8c39b24b4..0000000000 --- a/integration_tests/specs/method-channel/method-channel.ts +++ /dev/null @@ -1,62 +0,0 @@ -describe('MethodChannel', () => { - it('addMethodCallHandler multi params', async (done) => { - webf.methodChannel.addMethodCallHandler((method: string, args: any[]) => { - expect(method).toBe('helloworld'); - expect(args).toEqual(['abc', 1234, null, /* undefined will be converted to */ null, [], true, false, {name: 1}]); - done(); - }); - let result = await webf.methodChannel.invokeMethod('helloworld', 'abc', 1234, null, undefined, [], true, false, {name: 1}); - expect(result).toBe('method: helloworld'); - }); - - it('invokeMethod', async () => { - let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); - // TEST App will return method string - expect(result).toBe('method: helloworld'); - }); - - it('addMethodCallHandler', async (done) => { - webf.methodChannel.addMethodCallHandler((method: string, args: any[]) => { - expect(method).toBe('helloworld'); - expect(args).toEqual(['abc']); - done(); - }); - let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); - expect(result).toBe('method: helloworld'); - }); - - - it('removeMethodCallHandler', async (done: DoneFn) => { - var handler = (method: string, args: any[]) => { - done.fail('should not execute here.'); - }; - webf.methodChannel.addMethodCallHandler(handler); - webf.methodChannel.removeMethodCallHandler(handler); - let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); - expect(result).toBe('method: helloworld'); - done(); - }); - - it('addMethodCallHandler multi params with multi handler', async (done) => { - let handlerCount = 0; - webf.methodChannel.addMethodCallHandler((method: string, args: any[]) => { - handlerCount++; - expect(method).toBe('helloworld'); - expect(args).toEqual(['abc', 1234, null, /* undefined will be converted to */ null, [], true, false, {name: 1}]); - if(handlerCount == 2) { - done(); - } - }); - webf.methodChannel.addMethodCallHandler((method: string, args: any[]) => { - handlerCount++; - expect(method).toBe('helloworld'); - expect(args).toEqual(['abc', 1234, null, /* undefined will be converted to */ null, [], true, false, {name: 1}]); - if (handlerCount == 2) { - done(); - } - }); - let result = await webf.methodChannel.invokeMethod('helloworld', 'abc', 1234, null, undefined, [], true, false, {name: 1}); - expect(result).toBe('method: helloworld'); - expect(handlerCount).toBe(2); - }); -}); diff --git a/integration_tests/specs/modules/method-channel.ts b/integration_tests/specs/modules/method-channel.ts new file mode 100644 index 0000000000..83ea5d1471 --- /dev/null +++ b/integration_tests/specs/modules/method-channel.ts @@ -0,0 +1,46 @@ +describe('MethodChannel', () => { + it('addMethodCallHandler multi params', async () => { + webf.methodChannel.addMethodCallHandler('helloworld', (args: any) => { + expect(args).toEqual(['abc', 1234, null, /* undefined will be converted to */ null, [], true, false, {name: 1}]); + return 'from helloworld' + args[0]; + }); + let result = await webf.methodChannel.invokeMethod('helloworld', 'abc', 1234, null, undefined, [], true, false, {name: 1}); + expect(result).toBe('method: helloworld, return_type: String, return_value: from helloworldabc'); + }); + + it('invokeMethod', async () => { + let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); + // TEST App will return method string + expect(result).toBe('method: helloworld, return_type: Null, return_value: null'); + }); + + it('addMethodCallHandler', async () => { + webf.methodChannel.addMethodCallHandler('helloworld', (args: any[]) => { + expect(args).toEqual(['abc']); + return 0; + }); + let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); + expect(result).toBe('method: helloworld, return_type: int, return_value: 0'); + }); + + it('addMethodCallHandler can return value', async () => { + webf.methodChannel.addMethodCallHandler('helloworld', (args: any[]) => { + expect(args).toEqual(['abc']); + return true; + }); + let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); + expect(result).toBe('method: helloworld, return_type: bool, return_value: true'); + }); + + + it('removeMethodCallHandler', async (done: DoneFn) => { + var handler = (args: any[]) => { + done.fail('should not execute here.'); + }; + webf.methodChannel.addMethodCallHandler('helloworld', handler); + webf.methodChannel.removeMethodCallHandler('helloworld'); + let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); + expect(result).toBe('method: helloworld, return_type: Null, return_value: null'); + done(); + }); +}); diff --git a/integration_tests/specs/modules/raw_module.ts b/integration_tests/specs/modules/raw_module.ts new file mode 100644 index 0000000000..916c674003 --- /dev/null +++ b/integration_tests/specs/modules/raw_module.ts @@ -0,0 +1,65 @@ +describe('Modules.invokeModule', () => { + it('invokeModule can have no params', () => { + let result = webf.invokeModule('Demo', 'noParams'); + expect(result).toBe(true); + }); + it('invokeModule can accept int', () => { + let result = webf.invokeModule('Demo', 'callInt', 10); + expect(result).toBe(20); + }); + it('invokeModule can accept double', () => { + let result = webf.invokeModule('Demo', 'callDouble', 14.5); + expect(result).toBe(29.0); + }); + it('invokeModule can accept string', () => { + let result = webf.invokeModule('Demo', 'callString', 'helloworld'); + expect(result).toBe('HELLOWORLD'); + }); + it('invokeModule can accept array', () => { + let result = webf.invokeModule('Demo', 'callArray', [1, 2, 3, 4, 5]); + expect(result).toBe(15); + }); + it('invokeModule can accept null or undefined', () => { + expect(webf.invokeModule('Demo', 'callNull', null)).toBe(null); + expect(webf.invokeModule('Demo', 'callNull', undefined)).toBe(null); + }); + it('invokeModule can accept callback and receive value from callback', () => { + return new Promise((resolve, reject) => { + let callParams = 10; + let syncResult = webf.invokeModule('Demo', 'callAsyncFn', callParams, (err, data) => { + if (err) { + return reject(err); + } + expect(data).toEqual([1, '2', null, 4.0, { value: 1}]); + setTimeout(() => resolve()); + return 'success'; + }); + expect(syncResult).toBe(callParams); + }); + }); + it('invokeModule can accept callback and handle the error', () => { + return new Promise((resolve) => { + let syncResult = webf.invokeModule('Demo', 'callAsyncFnFail', null, (err, data) => { + expect(err).toBeInstanceOf(Error); + expect(err.message).toBe('Must to fail'); + setTimeout(() => resolve()); + return 'fail'; + }); + expect(syncResult).toBe(null); + }); + }); +}); + +describe('Module.addModuleListener', () => { + it('event params should works', (done) => { + webf.addWebfModuleListener('Demo', (event: CustomEvent, extra) => { + expect(event instanceof CustomEvent).toBe(true); + expect(event.type).toBe('click'); + expect(event.detail).toBe('helloworld'); + expect(extra).toEqual([1,2,3,4,5]); + setTimeout(() => done()); + return 'success'; + }); + webf.invokeModule('Demo', 'callToDispatchEvent'); + }); +}); \ No newline at end of file diff --git a/webf/CHANGELOG.md b/webf/CHANGELOG.md index 3743236dfe..d316669a4b 100644 --- a/webf/CHANGELOG.md +++ b/webf/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.13.0-beta.3 + +* Fix reload crash. + +## 0.13.0-beta.2 + +* Test for new bridge and css selector. + ## 0.12.0+2 **Bug Fixed** diff --git a/webf/example/android/app/src/main/AndroidManifest.xml b/webf/example/android/app/src/main/AndroidManifest.xml index 77093c1f4f..0bae586864 100644 --- a/webf/example/android/app/src/main/AndroidManifest.xml +++ b/webf/example/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> + nativeBindi AnonymousNativeFunction? fn = bindingObject.getAnonymousNativeFunctionFromId(id); if (fn == null) { print('WebF warning: can not find registered anonymous native function for id: $id bindingObject: $nativeBindingObject'); - toNativeValue(bindingObject, returnValue, null); + toNativeValue(returnValue, null, bindingObject); return; } try { @@ -108,7 +108,7 @@ void _invokeBindingMethodFromNativeImpl(Pointer nativeBindi AsyncAnonymousNativeFunction? fn = bindingObject.getAsyncAnonymousNativeFunctionFromId(id); if (fn == null) { print('WebF warning: can not find registered anonymous native async function for id: $id bindingObject: $nativeBindingObject'); - toNativeValue(bindingObject, returnValue, null); + toNativeValue(returnValue, null, bindingObject); return; } int contextId = values[1]; @@ -128,7 +128,7 @@ void _invokeBindingMethodFromNativeImpl(Pointer nativeBindi print('AsyncAnonymousFunction call resolved callback: $id arguments:[$result]'); } Pointer nativeValue = malloc.allocate(sizeOf()); - toNativeValue(bindingObject, nativeValue, result); + toNativeValue(nativeValue, result, bindingObject); callback(callbackContext, nativeValue, contextId, nullptr); }).catchError((e, stack) { String errorMessage = '$e\n$stack'; @@ -150,7 +150,7 @@ void _invokeBindingMethodFromNativeImpl(Pointer nativeBindi } catch (e, stack) { print('$e\n$stack'); } finally { - toNativeValue(bindingObject, returnValue, result); + toNativeValue(returnValue, result, bindingObject); } } @@ -158,21 +158,20 @@ void _invokeBindingMethodFromNativeImpl(Pointer nativeBindi void _dispatchEventToNative(Event event) { Pointer? pointer = event.currentTarget?.pointer; int? contextId = event.target?.contextId; - if (contextId != null && pointer != null) { + if (contextId != null && pointer != null && pointer.ref.invokeBindingMethodFromDart != nullptr) { BindingObject bindingObject = BindingBridge.getBindingObject(pointer); // Call methods implements at C++ side. DartInvokeBindingMethodsFromDart f = pointer.ref.invokeBindingMethodFromDart.asFunction(); Pointer rawEvent = event.toRaw().cast(); - bool isCustomEvent = event is CustomEvent; - List dispatchEventArguments = [event.type, rawEvent, isCustomEvent ? true : false]; + List dispatchEventArguments = [event.type, rawEvent]; if (isEnabledLog) { print('dispatch event to native side: target: ${event.target} arguments: $dispatchEventArguments'); } Pointer method = malloc.allocate(sizeOf()); - toNativeValue(bindingObject, method, 'dispatchEvent'); + toNativeValue(method, 'dispatchEvent'); Pointer allocatedNativeArguments = makeNativeValueArguments(bindingObject, dispatchEventArguments); Pointer returnValue = malloc.allocate(sizeOf()); @@ -209,7 +208,7 @@ abstract class BindingBridge { static void _bindObject(BindingObject object) { Pointer? nativeBindingObject = object.pointer; - if (nativeBindingObject != null) { + if (nativeBindingObject != null && !nativeBindingObject.ref.disposed) { _nativeObjects[nativeBindingObject.address] = object; nativeBindingObject.ref.invokeBindingMethodFromNative = _invokeBindingMethodFromNative; } diff --git a/webf/lib/src/bridge/bridge.dart b/webf/lib/src/bridge/bridge.dart index 4bb06ebffb..868d06d777 100644 --- a/webf/lib/src/bridge/bridge.dart +++ b/webf/lib/src/bridge/bridge.dart @@ -3,10 +3,7 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import 'dart:async'; - import 'package:flutter/foundation.dart'; -import 'package:flutter/scheduler.dart'; import 'package:webf/module.dart'; import 'package:webf/launcher.dart'; @@ -35,30 +32,18 @@ int initBridge(WebFViewController view) { int contextId = -1; - // We should schedule addPersistentFrameCallback() to the next frame because of initBridge() - // will be called from persistent frame callbacks and cause infinity loops. - if (_firstView) { - Future.microtask(() { - // Port flutter's frame callback into bridge. - SchedulerBinding.instance.addPersistentFrameCallback((_) { - flushUICommand(view); - }); - }); - } + List dartMethods = makeDartMethodsData(); if (_firstView) { - initJSPagePool(kWebFJSPagePoolSize); + initJSPagePool(kWebFJSPagePoolSize, dartMethods); _firstView = false; contextId = 0; } else { - contextId = allocateNewPage(); + contextId = allocateNewPage(dartMethods); if (contextId == -1) { throw Exception('Can\' allocate new webf bridge: bridge count had reach the maximum size.'); } } - // Register methods first to share ptrs for bridge polyfill. - registerDartMethodsToCpp(contextId); - return contextId; } diff --git a/webf/lib/src/bridge/from_native.dart b/webf/lib/src/bridge/from_native.dart index 50c299f29a..cd11a32275 100644 --- a/webf/lib/src/bridge/from_native.dart +++ b/webf/lib/src/bridge/from_native.dart @@ -3,7 +3,7 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import 'dart:convert'; +import 'dart:async'; import 'dart:ffi'; import 'dart:typed_data'; @@ -78,47 +78,52 @@ void freeNativeString(Pointer pointer) { // 6. Call from C. // Register InvokeModule -typedef NativeAsyncModuleCallback = Void Function( - Pointer callbackContext, Int32 contextId, Pointer errmsg, Pointer json); -typedef DartAsyncModuleCallback = void Function( - Pointer callbackContext, int contextId, Pointer errmsg, Pointer json); +typedef NativeAsyncModuleCallback = Pointer Function( + Pointer callbackContext, Int32 contextId, Pointer errmsg, Pointer ptr); +typedef DartAsyncModuleCallback = Pointer Function( + Pointer callbackContext, int contextId, Pointer errmsg, Pointer ptr); -typedef NativeInvokeModule = Pointer Function( +typedef NativeInvokeModule = Pointer Function( Pointer callbackContext, Int32 contextId, Pointer module, Pointer method, - Pointer params, + Pointer params, Pointer>); -String invokeModule(Pointer callbackContext, int contextId, String moduleName, String method, String? params, +dynamic invokeModule(Pointer callbackContext, int contextId, String moduleName, String method, params, DartAsyncModuleCallback callback) { WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; - String result = ''; + dynamic result; try { - void invokeModuleCallback({String? error, data}) { + Future invokeModuleCallback({String? error, data}) { + Completer completer = Completer(); // To make sure Promise then() and catch() executed before Promise callback called at JavaScript side. // We should make callback always async. Future.microtask(() { + Pointer callbackResult = nullptr; if (error != null) { Pointer errmsgPtr = error.toNativeUtf8(); - callback(callbackContext, contextId, errmsgPtr, nullptr); + callbackResult = callback(callbackContext, contextId, errmsgPtr, nullptr); malloc.free(errmsgPtr); } else { - Pointer dataPtr = stringToNativeString(jsonEncode(data)); - callback(callbackContext, contextId, nullptr, dataPtr); - freeNativeString(dataPtr); + Pointer dataPtr = malloc.allocate(sizeOf()); + toNativeValue(dataPtr, data); + callbackResult = callback(callbackContext, contextId, nullptr, dataPtr); + malloc.free(dataPtr); + } + if (isEnabledLog) { + print('Invoke module callback from(name: $moduleName method: $method, params: $params) return: ${fromNativeValue(callbackResult)}'); } - }); - } - if (isEnabledLog) { - print('Invoke module name: $moduleName method: $method, params: ${(params != null && params != '""') ? jsonDecode(params) : null}'); + completer.complete(fromNativeValue(callbackResult)); + }); + return completer.future; } result = controller.module.moduleManager.invokeModule( - moduleName, method, (params != null && params != '""') ? jsonDecode(params) : null, invokeModuleCallback); + moduleName, method, params, invokeModuleCallback); } catch (e, stack) { if (isEnabledLog) { print('Invoke module failed: $e\n$stack'); @@ -127,19 +132,25 @@ String invokeModule(Pointer callbackContext, int contextId, String moduleN callback(callbackContext, contextId, error.toNativeUtf8(), nullptr); } + if (isEnabledLog) { + print('Invoke module name: $moduleName method: $method, params: $params return: $result'); + } + return result; } -Pointer _invokeModule( +Pointer _invokeModule( Pointer callbackContext, int contextId, Pointer module, Pointer method, - Pointer params, + Pointer params, Pointer> callback) { - String result = invokeModule(callbackContext, contextId, nativeStringToString(module), nativeStringToString(method), - params == nullptr ? null : nativeStringToString(params), callback.asFunction()); - return stringToNativeString(result); + dynamic result = invokeModule(callbackContext, contextId, nativeStringToString(module), nativeStringToString(method), + fromNativeValue(params), callback.asFunction()); + Pointer returnValue = malloc.allocate(sizeOf()); + toNativeValue(returnValue, result); + return returnValue; } final Pointer> _nativeInvokeModule = Pointer.fromFunction(_invokeModule); @@ -392,15 +403,6 @@ final List _dartNativeMethods = [ _nativeOnJsLog.address, ]; -typedef NativeRegisterDartMethods = Void Function(Int32 contextId, Pointer methodBytes, Int32 length); -typedef DartRegisterDartMethods = void Function(int contextId, Pointer methodBytes, int length); - -final DartRegisterDartMethods _registerDartMethods = - WebFDynamicLibrary.ref.lookup>('registerDartMethods').asFunction(); - -void registerDartMethodsToCpp(int contextId) { - Pointer bytes = malloc.allocate(sizeOf() * _dartNativeMethods.length); - Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); - nativeMethodList.setAll(0, _dartNativeMethods); - _registerDartMethods(contextId, bytes, _dartNativeMethods.length); +List makeDartMethodsData() { + return _dartNativeMethods; } diff --git a/webf/lib/src/bridge/native_types.dart b/webf/lib/src/bridge/native_types.dart index 81391e09ad..6daeda92b4 100644 --- a/webf/lib/src/bridge/native_types.dart +++ b/webf/lib/src/bridge/native_types.dart @@ -29,6 +29,8 @@ class RawEvent extends Struct { external Pointer bytes; @Int64() external int length; + @Int8() + external int is_custom_event; } class EventDispatchResult extends Struct { @@ -97,12 +99,20 @@ typedef DartInvokeBindingMethodsFromDart = void Function(Pointer return_value, Pointer method, int argc, Pointer argv); class NativeBindingObject extends Struct { + @Bool() + external bool disposed; external Pointer instance; external Pointer> invokeBindingMethodFromDart; // Shared method called by JS side. external Pointer> invokeBindingMethodFromNative; } +Pointer allocateNewBindingObject() { + Pointer pointer = malloc.allocate(sizeOf()); + pointer.ref.disposed = false; + return pointer; +} + class NativePerformanceEntry extends Struct { external Pointer name; external Pointer entryType; diff --git a/webf/lib/src/bridge/native_value.dart b/webf/lib/src/bridge/native_value.dart index 8b9d0818ca..2e53e5855c 100644 --- a/webf/lib/src/bridge/native_value.dart +++ b/webf/lib/src/bridge/native_value.dart @@ -78,7 +78,7 @@ dynamic fromNativeValue(Pointer nativeValue) { } } -void toNativeValue(BindingObject ownerBindingObject, Pointer target, value) { +void toNativeValue(Pointer target, value, [BindingObject? ownerBindingObject]) { if (value == null) { target.ref.tag = JSValueType.TAG_NULL.index; } else if (value is int) { @@ -105,13 +105,13 @@ void toNativeValue(BindingObject ownerBindingObject, Pointer target Pointer lists = malloc.allocate(sizeOf() * value.length); target.ref.u = lists.address; for(int i = 0; i < value.length; i ++) { - toNativeValue(ownerBindingObject, lists.elementAt(i), value[i]); + toNativeValue(lists.elementAt(i), value[i], ownerBindingObject); } - } else if (value is AsyncAnonymousNativeFunction) { + } else if (value is AsyncAnonymousNativeFunction && ownerBindingObject != null) { int id = ownerBindingObject.setAsyncAnonymousNativeFunction(value); target.ref.tag = JSValueType.TAG_ASYNC_FUNCTION.index; target.ref.u = id; - } else if (value is AnonymousNativeFunction) { + } else if (value is AnonymousNativeFunction && ownerBindingObject != null) { int id = ownerBindingObject.setAnonymousNativeFunction(value); target.ref.tag = JSValueType.TAG_FUNCTION.index; target.ref.u = id; @@ -126,7 +126,7 @@ Pointer makeNativeValueArguments(BindingObject ownerBindingObject, Pointer buffer = malloc.allocate(sizeOf() * args.length); for(int i = 0; i < args.length; i ++) { - toNativeValue(ownerBindingObject, buffer.elementAt(i), args[i]); + toNativeValue(buffer.elementAt(i), args[i], ownerBindingObject); } return buffer.cast(); diff --git a/webf/lib/src/bridge/to_native.dart b/webf/lib/src/bridge/to_native.dart index a7de3baced..3b4b0340d9 100644 --- a/webf/lib/src/bridge/to_native.dart +++ b/webf/lib/src/bridge/to_native.dart @@ -61,35 +61,35 @@ WebFInfo getWebFInfo() { } // Register invokeEventListener -typedef NativeInvokeEventListener = Void Function( - Int32 contextId, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); -typedef DartInvokeEventListener = void Function( - int contextId, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); +typedef NativeInvokeEventListener = Pointer Function( + Int32 contextId, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); +typedef DartInvokeEventListener = Pointer Function( + int contextId, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); final DartInvokeEventListener _invokeModuleEvent = WebFDynamicLibrary.ref.lookup>('invokeModuleEvent').asFunction(); -void invokeModuleEvent(int contextId, String moduleName, Event? event, String extra) { +dynamic invokeModuleEvent(int contextId, String moduleName, Event? event, extra) { if (WebFController.getControllerOfJSContextId(contextId) == null) { - return; + return null; } Pointer nativeModuleName = stringToNativeString(moduleName); Pointer rawEvent = event == null ? nullptr : event.toRaw().cast(); - _invokeModuleEvent(contextId, nativeModuleName, event == null ? nullptr : event.type.toNativeUtf8(), rawEvent, - stringToNativeString(extra)); + Pointer extraData = malloc.allocate(sizeOf()); + toNativeValue(extraData, extra); + Pointer dispatchResult = _invokeModuleEvent( + contextId, nativeModuleName, event == null ? nullptr : event.type.toNativeUtf8(), rawEvent, extraData); freeNativeString(nativeModuleName); + dynamic result = fromNativeValue(dispatchResult); + malloc.free(dispatchResult); + return result; } -typedef DartDispatchEvent = int Function( - int contextId, - Pointer nativeBindingObject, - Pointer eventType, - Pointer nativeEvent, - int isCustomEvent - ); +typedef DartDispatchEvent = int Function(int contextId, Pointer nativeBindingObject, + Pointer eventType, Pointer nativeEvent, int isCustomEvent); -void emitModuleEvent(int contextId, String moduleName, Event? event, String extra) { - invokeModuleEvent(contextId, moduleName, event, extra); +dynamic emitModuleEvent(int contextId, String moduleName, Event? event, extra) { + return invokeModuleEvent(contextId, moduleName, event, extra); } // Register createScreen @@ -172,14 +172,18 @@ void parseHTML(int contextId, String code) { } // Register initJsEngine -typedef NativeInitJSPagePool = Void Function(Int32 poolSize); -typedef DartInitJSPagePool = void Function(int poolSize); +typedef NativeInitJSPagePool = Void Function(Int32 poolSize, Pointer dartMethods, Int32 methodsLength); +typedef DartInitJSPagePool = void Function(int poolSize, Pointer dartMethods, int length); final DartInitJSPagePool _initJSPagePool = WebFDynamicLibrary.ref.lookup>('initJSPagePool').asFunction(); -void initJSPagePool(int poolSize) { - _initJSPagePool(poolSize); +void initJSPagePool(int poolSize, List dartMethods) { + Pointer bytes = malloc.allocate(sizeOf() * dartMethods.length); + Uint64List nativeMethodList = bytes.asTypedList(dartMethods.length); + nativeMethodList.setAll(0, dartMethods); + + _initJSPagePool(poolSize, bytes, dartMethods.length); } typedef NativeDisposePage = Void Function(Int32 contextId); @@ -192,24 +196,25 @@ void disposePage(int contextId) { _disposePage(contextId); } -typedef NativeAllocateNewPage = Int32 Function(Int32); -typedef DartAllocateNewPage = int Function(int); +typedef NativeAllocateNewPage = Int32 Function(Int32, Pointer dartMethods, Int32 methodsLength); +typedef DartAllocateNewPage = int Function(int, Pointer dartMethods, int methodsLength); final DartAllocateNewPage _allocateNewPage = WebFDynamicLibrary.ref.lookup>('allocateNewPage').asFunction(); -int allocateNewPage([int targetContextId = -1]) { - return _allocateNewPage(targetContextId); +int allocateNewPage(List dartMethods, [int targetContextId = -1]) { + Pointer bytes = malloc.allocate(sizeOf() * dartMethods.length); + Uint64List nativeMethodList = bytes.asTypedList(dartMethods.length); + nativeMethodList.setAll(0, dartMethods); + + return _allocateNewPage(targetContextId, bytes, dartMethods.length); } typedef NativeRegisterPluginByteCode = Void Function(Pointer bytes, Int32 length, Pointer pluginName); typedef DartRegisterPluginByteCode = void Function(Pointer bytes, int length, Pointer pluginName); -final DartRegisterPluginByteCode _registerPluginByteCode = WebFDynamicLibrary - .ref - .lookup>( - 'registerPluginByteCode') - .asFunction(); +final DartRegisterPluginByteCode _registerPluginByteCode = + WebFDynamicLibrary.ref.lookup>('registerPluginByteCode').asFunction(); void registerPluginByteCode(Uint8List bytecode, String name) { Pointer bytes = malloc.allocate(sizeOf() * bytecode.length); @@ -230,25 +235,26 @@ bool profileModeEnabled() { } // Regisdster reloadJsContext -typedef NativeReloadJSContext = Void Function(Int32 contextId); -typedef DartReloadJSContext = void Function(int contextId); +typedef NativeReloadJSContext = Void Function(Int32 contextId, Pointer dartMethods, Int32 methodsLength); +typedef DartReloadJSContext = void Function(int contextId, Pointer dartMethods, int methodsLength); final DartReloadJSContext _reloadJSContext = WebFDynamicLibrary.ref.lookup>('reloadJsContext').asFunction(); -Future reloadJSContext(int contextId) async { +Future reloadJSContext(int contextId, List dartMethods) async { Completer completer = Completer(); Future.microtask(() { - _reloadJSContext(contextId); + Pointer bytes = malloc.allocate(sizeOf() * dartMethods.length); + Uint64List nativeMethodList = bytes.asTypedList(dartMethods.length); + nativeMethodList.setAll(0, dartMethods); + _reloadJSContext(contextId, bytes, dartMethods.length); completer.complete(); }); return completer.future; } -typedef NativeDispatchUITask = Void Function( - Int32 contextId, Pointer context, Pointer callback); -typedef DartDispatchUITask = void Function( - int contextId, Pointer context, Pointer callback); +typedef NativeDispatchUITask = Void Function(Int32 contextId, Pointer context, Pointer callback); +typedef DartDispatchUITask = void Function(int contextId, Pointer context, Pointer callback); final DartDispatchUITask _dispatchUITask = WebFDynamicLibrary.ref.lookup>('dispatchUITask').asFunction(); @@ -336,16 +342,14 @@ const int args01StringMemOffset = 2; const int args02StringMemOffset = 3; const int nativePtrMemOffset = 4; -final bool isEnabledLog = kDebugMode && Platform.environment['ENABLE_WEBF_JS_LOG'] == 'true'; +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 rawMemory = + nativeCommandItems.cast().asTypedList(commandLength * nativeCommandSize).toList(growable: false); List results = List.generate(commandLength, (int _i) { int i = _i * nativeCommandSize; UICommand command = UICommand(); @@ -417,8 +421,7 @@ void flushUICommandWithContextId(int contextId) { } void flushUICommand(WebFViewController view) { - Pointer nativeCommandItems = - _getUICommandItems(view.contextId); + Pointer nativeCommandItems = _getUICommandItems(view.contextId); int commandLength = _getUICommandItemSize(view.contextId); if (commandLength == 0 || nativeCommandItems == nullptr) { @@ -429,8 +432,7 @@ void flushUICommand(WebFViewController view) { PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_START); } - List commands = readNativeUICommandToDart( - nativeCommandItems, commandLength, view.contextId); + List commands = readNativeUICommandToDart(nativeCommandItems, commandLength, view.contextId); SchedulerBinding.instance.scheduleFrame(); @@ -450,8 +452,7 @@ void flushUICommand(WebFViewController view) { try { switch (commandType) { case UICommandType.createElement: - view.createElement( - id, nativePtr.cast(), command.args[0]); + view.createElement(id, nativePtr.cast(), command.args[0]); break; case UICommandType.createDocument: view.initDocument(id, nativePtr.cast()); @@ -460,15 +461,13 @@ void flushUICommand(WebFViewController view) { view.initWindow(id, nativePtr.cast()); break; case UICommandType.createTextNode: - view.createTextNode( - id, nativePtr.cast(), command.args[0]); + view.createTextNode(id, nativePtr.cast(), command.args[0]); break; case UICommandType.createComment: - view - .createComment(id, nativePtr.cast()); + view.createComment(id, nativePtr.cast()); break; case UICommandType.disposeEventTarget: - view.disposeEventTarget(id); + view.disposeEventTarget(id, nativePtr.cast()); break; case UICommandType.addEvent: view.addEvent(id, command.args[0]); @@ -504,8 +503,7 @@ void flushUICommand(WebFViewController view) { view.removeAttribute(id, key); break; case UICommandType.createDocumentFragment: - view.createDocumentFragment( - id, nativePtr.cast()); + view.createDocumentFragment(id, nativePtr.cast()); break; default: break; diff --git a/webf/lib/src/css/css_animation.dart b/webf/lib/src/css/css_animation.dart index 0cb0426d63..f28ddc6813 100644 --- a/webf/lib/src/css/css_animation.dart +++ b/webf/lib/src/css/css_animation.dart @@ -197,7 +197,7 @@ mixin CSSAnimationMixin on RenderStyle { final fillMode = camelize(_getSingleString(animationFillMode, i)); EffectTiming? options = EffectTiming( - duration: CSSTime.parseTime(duration)!.toDouble(), + duration: CSSTime.parseNotNegativeTime(duration)!.toDouble(), easing: timingFunction, delay: CSSTime.parseTime(delay)!.toDouble(), fill: FillMode.values.firstWhere((element) { diff --git a/webf/lib/src/css/transition.dart b/webf/lib/src/css/transition.dart index c3858d80d7..3254b6659b 100644 --- a/webf/lib/src/css/transition.dart +++ b/webf/lib/src/css/transition.dart @@ -385,7 +385,7 @@ mixin CSSTransitionMixin on RenderStyle { // [duration, function, delay] if (transitionOptions != null) { return EffectTiming( - duration: CSSTime.parseTime(transitionOptions[0])!.toDouble(), + duration: CSSTime.parseNotNegativeTime(transitionOptions[0])!.toDouble(), easing: transitionOptions[1], delay: CSSTime.parseTime(transitionOptions[2])!.toDouble(), // In order for CSS Transitions to be seeked backwards, they need to have their fill mode set to backwards diff --git a/webf/lib/src/css/values/time.dart b/webf/lib/src/css/values/time.dart index 59f5828167..74dcabfe1f 100644 --- a/webf/lib/src/css/values/time.dart +++ b/webf/lib/src/css/values/time.dart @@ -19,16 +19,35 @@ class CSSTime { return (value == _0s || value == _0ms || _timeRegExp.firstMatch(value) != null); } - static int? parseTime(String input) { - if (_cachedParsedTime.containsKey(input)) { - return _cachedParsedTime[input]; - } + static int? _parseTimeValue(String input) { int? milliseconds; if (input.endsWith(MILLISECONDS)) { milliseconds = double.tryParse(input.split(MILLISECONDS)[0])!.toInt(); } else if (input.endsWith(SECOND)) { milliseconds = (double.tryParse(input.split(SECOND)[0])! * 1000).toInt(); } + return milliseconds; + } + + static int? parseTime(String input) { + if (_cachedParsedTime.containsKey(input)) { + return _cachedParsedTime[input]; + } + + int? milliseconds = _parseTimeValue(input); + + return _cachedParsedTime[input] = milliseconds; + } + + static int? parseNotNegativeTime(String input) { + if (_cachedParsedTime.containsKey(input)) { + return _cachedParsedTime[input]; + } + + int? milliseconds = _parseTimeValue(input); + if (milliseconds != null && milliseconds < 0) { + milliseconds = 0; + } return _cachedParsedTime[input] = milliseconds; } diff --git a/webf/lib/src/devtools/server.dart b/webf/lib/src/devtools/server.dart index 5822429cd3..53b6d2fe0c 100644 --- a/webf/lib/src/devtools/server.dart +++ b/webf/lib/src/devtools/server.dart @@ -71,9 +71,6 @@ void attachInspector(int contextId) { } void initInspectorServerNativeBinding(int contextId) { - final DartRegisterDartMethods _registerInspectorServerDartMethods = WebFDynamicLibrary.ref - .lookup>('registerInspectorDartMethods') - .asFunction(); final Pointer> _nativeInspectorMessage = Pointer.fromFunction(_onInspectorMessage); final Pointer> _nativeRegisterInspectorMessageCallback = @@ -90,8 +87,6 @@ void initInspectorServerNativeBinding(int contextId) { Pointer bytes = malloc.allocate(_dartNativeMethods.length * sizeOf()); Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); - - _registerInspectorServerDartMethods(contextId, bytes, _dartNativeMethods.length); } void serverIsolateEntryPoint(SendPort isolateToMainStream) { diff --git a/webf/lib/src/devtools/service.dart b/webf/lib/src/devtools/service.dart index 0f0d71edb0..5a5bd339ce 100644 --- a/webf/lib/src/devtools/service.dart +++ b/webf/lib/src/devtools/service.dart @@ -5,26 +5,18 @@ import 'dart:isolate'; import 'dart:ffi'; -import 'dart:typed_data'; - -import 'package:ffi/ffi.dart'; 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); -void _postTaskToInspectorThread(int contextId, Pointer context, Pointer callback) { - ChromeDevToolsService? devTool = ChromeDevToolsService.getDevToolOfContextId(contextId); - if (devTool != null) { - devTool.isolateServerPort!.send(InspectorPostTaskMessage(context.address, callback.address)); - } -} - -final Pointer> _nativePostTaskToInspectorThread = - Pointer.fromFunction(_postTaskToInspectorThread); - -final List _dartNativeMethods = [_nativePostTaskToInspectorThread.address]; +// void _postTaskToInspectorThread(int contextId, Pointer context, Pointer callback) { +// ChromeDevToolsService? devTool = ChromeDevToolsService.getDevToolOfContextId(contextId); +// if (devTool != null) { +// devTool.isolateServerPort!.send(InspectorPostTaskMessage(context.address, callback.address)); +// } +// } void spawnIsolateInspectorServer(ChromeDevToolsService devTool, WebFController controller, {int port = INSPECTOR_DEFAULT_PORT, String? address}) { @@ -113,15 +105,4 @@ class ChromeDevToolsService extends DevToolsService { controller!.view.debugDOMTreeChanged = _uiInspector!.onDOMTreeChanged; _isolateServerPort!.send(InspectorReload(_controller!.view.contextId)); } - - // @TODO: Implement and remove. - // ignore: unused_element - static bool _registerUIDartMethodsToCpp(int contextId) { - final DartRegisterDartMethods _registerDartMethods = WebFDynamicLibrary.ref.lookup>('registerUIDartMethods').asFunction(); - Pointer bytes = malloc.allocate(_dartNativeMethods.length * sizeOf()); - Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); - nativeMethodList.setAll(0, _dartNativeMethods); - _registerDartMethods(contextId, bytes, _dartNativeMethods.length); - return true; - } } diff --git a/webf/lib/src/dom/bounding_client_rect.dart b/webf/lib/src/dom/bounding_client_rect.dart index ac9406dce7..cce811598c 100644 --- a/webf/lib/src/dom/bounding_client_rect.dart +++ b/webf/lib/src/dom/bounding_client_rect.dart @@ -4,7 +4,6 @@ */ import 'dart:ffi'; -import 'package:ffi/ffi.dart'; import 'package:webf/bridge.dart'; import 'package:webf/foundation.dart'; @@ -21,7 +20,7 @@ class BoundingClientRect extends BindingObject { final double left; BoundingClientRect(this.x, this.y, this.width, this.height, this.top, this.right, this.bottom, this.left) - : _pointer = malloc.allocate(sizeOf()), + : _pointer = allocateNewBindingObject(), super(); final Pointer _pointer; diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 5cee361c0f..c465982463 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -14,6 +14,7 @@ import 'package:webf/module.dart'; import 'package:webf/rendering.dart'; import 'package:webf/src/css/query_selector.dart' as QuerySelector; import 'package:webf/src/dom/element_registry.dart' as element_registry; +import 'package:webf/src/foundation/cookie_jar.dart'; import 'package:webf/widget.dart'; class Document extends Node { @@ -54,6 +55,8 @@ class Document extends Node { Element? focusedElement; + CookieJar cookie_ = CookieJar(); + // Returns the Window object of the active document. // https://html.spec.whatwg.org/multipage/window-object.html#dom-document-defaultview-dev Window get defaultView => controller.view.window; @@ -95,6 +98,27 @@ class Document extends Node { } } + @override + void setBindingProperty(String key, value) { + switch(key) { + case 'cookie': + cookie_.setCookie(value); + break; + } + + super.setBindingProperty(key, value); + } + + @override + getBindingProperty(String key) { + switch(key) { + case 'cookie': + return cookie_.cookie(); + } + + return super.getBindingProperty(key); + } + @override invokeBindingMethod(String method, List args) { switch (method) { diff --git a/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart b/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart index e891a37ed3..ffa0bb2aae 100644 --- a/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart +++ b/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart @@ -7,7 +7,6 @@ import 'dart:typed_data'; import 'dart:ui'; import 'dart:ffi' as ffi; -import 'package:ffi/ffi.dart'; import 'package:flutter/painting.dart'; import 'package:webf/bridge.dart'; import 'package:webf/foundation.dart'; @@ -45,7 +44,7 @@ typedef CanvasAction = void Function(Canvas, Size); class CanvasRenderingContext2D extends BindingObject { CanvasRenderingContext2D(this.canvas) - : _pointer = malloc.allocate(ffi.sizeOf()), + : _pointer = allocateNewBindingObject(), super(); final ffi.Pointer _pointer; diff --git a/webf/lib/src/dom/elements/img.dart b/webf/lib/src/dom/elements/img.dart index e8a4d95097..dbe009582b 100644 --- a/webf/lib/src/dom/elements/img.dart +++ b/webf/lib/src/dom/elements/img.dart @@ -494,6 +494,7 @@ class ImageElement extends Element { SchedulerBinding.instance.addPostFrameCallback((timeStamp) { _dispatchLoadEvent(); }); + SchedulerBinding.instance.scheduleFrame(); } } diff --git a/webf/lib/src/dom/event.dart b/webf/lib/src/dom/event.dart index 16cb336b2d..3c1966d8e1 100644 --- a/webf/lib/src/dom/event.dart +++ b/webf/lib/src/dom/event.dart @@ -194,7 +194,7 @@ class Event { bubbles = false; } - Pointer toRaw([int extraLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { Pointer event = malloc.allocate(sizeOf()); EventTarget? _target = target; @@ -218,6 +218,7 @@ class Event { bytes.asTypedList(methods.length).setAll(0, methods); event.ref.bytes = bytes; event.ref.length = methods.length; + event.ref.is_custom_event = isCustomEvent ? 1 : 0; return event.cast(); } @@ -234,7 +235,7 @@ class PopStateEvent extends Event { PopStateEvent({this.state}) : super(EVENT_POP_STATE); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [jsonEncode(state).toNativeUtf8().address]; Pointer rawEvent = super.toRaw(methods.length).cast(); @@ -274,10 +275,10 @@ class UIEvent extends Event { }) : super(type, bubbles: bubbles, cancelable: cancelable, composed: composed); @override - Pointer toRaw([int extraMethodsLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [doubleToUint64(detail), view?.pointer?.address ?? nullptr.address, doubleToUint64(which)]; - Pointer rawEvent = super.toRaw(methods.length + extraMethodsLength).cast(); + Pointer rawEvent = super.toRaw(methods.length + extraLength).cast(); int currentStructSize = rawEvent.ref.length + methods.length; Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); @@ -307,7 +308,7 @@ class MouseEvent extends UIEvent { detail: detail, view: view, which: which, bubbles: true, cancelable: true, composed: false); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [ doubleToUint64(clientX), doubleToUint64(clientY), @@ -315,7 +316,7 @@ class MouseEvent extends UIEvent { doubleToUint64(offsetY) ]; - Pointer rawEvent = super.toRaw(methods.length + methodLength).cast(); + Pointer rawEvent = super.toRaw(methods.length + extraLength).cast(); int currentStructSize = rawEvent.ref.length + methods.length; Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); @@ -352,7 +353,7 @@ class GestureEvent extends Event { }) : super(type, bubbles: bubbles, cancelable: cancelable, composed: composed); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [ stringToNativeString(state).address, stringToNativeString(direction).address, @@ -377,21 +378,25 @@ class GestureEvent extends Event { /// reference: http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html#interface-CustomEvent /// Attention: Detail now only can be a string. class CustomEvent extends Event { - String detail; + dynamic detail; CustomEvent( String type, { - this.detail = '', + this.detail = null, bool bubbles = false, bool cancelable = false, bool composed = false, }) : super(type, bubbles: bubbles, cancelable: cancelable, composed: composed); @override - Pointer toRaw([int methodLength = 0]) { - List methods = [detail.toNativeUtf8().address]; + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { + Pointer detailValue = malloc.allocate(sizeOf()); + toNativeValue(detailValue, detail); + List methods = [ + detailValue.address + ]; - Pointer rawEvent = super.toRaw(methods.length).cast(); + Pointer rawEvent = super.toRaw(methods.length, true).cast(); int currentStructSize = rawEvent.ref.length + methods.length; Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); @@ -410,7 +415,7 @@ class InputEvent extends UIEvent { final String data; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [stringToNativeString(inputType).address, stringToNativeString(data).address]; Pointer rawEvent = super.toRaw(methods.length).cast(); @@ -469,7 +474,7 @@ class MediaError extends Event { final String message; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [code, stringToNativeString(message).address]; Pointer rawEvent = super.toRaw(methods.length).cast(); @@ -494,10 +499,10 @@ class MessageEvent extends Event { final String lastEventId; final String source; - MessageEvent(this.data, {this.origin = ''}) : super(EVENT_MESSAGE); + MessageEvent(this.data, {this.origin = '', this.lastEventId = '', this.source = ''}) : super(EVENT_MESSAGE); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { Pointer nativeData = malloc.allocate(sizeOf()); toNativeValue(nativeData, data); List methods = [ @@ -506,7 +511,7 @@ class MessageEvent extends Event { stringToNativeString(lastEventId).address, stringToNativeString(source).address ]; - + Pointer rawEvent = super.toRaw(methods.length).cast(); int currentStructSize = rawEvent.ref.length + methods.length; Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); @@ -531,7 +536,7 @@ class CloseEvent extends Event { CloseEvent(this.code, this.reason, this.wasClean) : super(EVENT_CLOSE); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [code, stringToNativeString(reason).address, wasClean ? 1 : 0]; Pointer rawEvent = super.toRaw(methods.length).cast(); @@ -549,7 +554,7 @@ class IntersectionChangeEvent extends Event { final double intersectionRatio; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [doubleToUint64(intersectionRatio)]; Pointer rawEvent = super.toRaw(methods.length).cast(); @@ -588,7 +593,7 @@ class TouchEvent extends UIEvent { bool shiftKey = false; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [ touches.toNative().address, targetTouches.toNative().address, @@ -709,7 +714,7 @@ class AnimationEvent extends Event { String pseudoElement; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [ stringToNativeString(animationName).address, doubleToUint64(elapsedTime), diff --git a/webf/lib/src/dom/screen.dart b/webf/lib/src/dom/screen.dart index 80a639fd33..076d5f57d2 100644 --- a/webf/lib/src/dom/screen.dart +++ b/webf/lib/src/dom/screen.dart @@ -3,13 +3,13 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ import 'dart:ui'; - import 'package:webf/foundation.dart'; +import 'package:webf/bridge.dart'; // 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 { - Screen([BindingContext? context]) : super(context); + Screen(int contextId) : super(BindingContext(contextId, allocateNewBindingObject())); @override getBindingProperty(String key) { diff --git a/webf/lib/src/dom/window.dart b/webf/lib/src/dom/window.dart index e19ff8d726..aa3dd2e943 100644 --- a/webf/lib/src/dom/window.dart +++ b/webf/lib/src/dom/window.dart @@ -3,8 +3,6 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ import 'dart:ui'; -import 'dart:ffi' as ffi; -import 'package:ffi/ffi.dart'; import 'package:webf/bridge.dart'; import 'package:webf/dom.dart'; @@ -19,7 +17,8 @@ class Window extends EventTarget { final Screen screen; Window(BindingContext? context, this.document) - : screen = Screen(BindingContext(context!.contextId, malloc.allocate(ffi.sizeOf()))), super(context); + : screen = Screen(context!.contextId), + super(context); @override EventTarget? get parentEventTarget => null; @@ -108,7 +107,9 @@ class Window extends EventTarget { @override void dispatchEvent(Event event) { // 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) { + if (contextId != null && event.type == EVENT_DOM_CONTENT_LOADED || + event.type == EVENT_LOAD || + event.type == EVENT_ERROR) { flushUICommandWithContextId(contextId!); } super.dispatchEvent(event); diff --git a/webf/lib/src/foundation/cookie_jar.dart b/webf/lib/src/foundation/cookie_jar.dart new file mode 100644 index 0000000000..885f3a9dda --- /dev/null +++ b/webf/lib/src/foundation/cookie_jar.dart @@ -0,0 +1,38 @@ +// Legacy implements. +// TODO: should read cookie values from http requests. +class CookieJar { + final Map _pairs = {}; + void setCookie(String value) { + value = value.trim(); + + RegExp pattern = RegExp(r'^[^=]*=([^;]*)'); + + if (!value.contains('=')) { + _pairs[''] = value; + } else { + int idx = value.indexOf('='); + String key = value.substring(0, idx); + // Only allow to set a single cookie at a time + // Find first cookie value if multiple cookie set + RegExpMatch? match = pattern.firstMatch(value); + + if (match != null && match[1] != null) { + value = match[1]!; + + if (key.isEmpty && value.isEmpty) { + return; + } + } + _pairs[key] = value; + } + } + + String cookie() { + List cookiePairs = List.generate(_pairs.length, (index) { + String key = _pairs.keys.elementAt(index); + String value = _pairs.values.elementAt(index); + return '$key=$value'; + }); + return cookiePairs.join('; '); + } +} diff --git a/webf/lib/src/launcher/controller.dart b/webf/lib/src/launcher/controller.dart index 551ae2cf3b..a15a76d1ae 100644 --- a/webf/lib/src/launcher/controller.dart +++ b/webf/lib/src/launcher/controller.dart @@ -11,6 +11,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'dart:ui' as ui; +import 'package:ffi/ffi.dart'; import 'package:flutter/animation.dart'; import 'package:flutter/widgets.dart' show RenderObjectElement; import 'package:flutter/foundation.dart'; @@ -146,6 +147,14 @@ class WebFViewController implements WidgetsBindingObserver, ElementsBindingObser if (kProfileMode) { PerformanceTiming.instance().mark(PERF_ELEMENT_MANAGER_INIT_END); } + + SchedulerBinding.instance.addPostFrameCallback(_postFrameCallback); + } + + void _postFrameCallback(Duration timeStamp) { + if (disposed) return; + flushUICommand(this); + SchedulerBinding.instance.addPostFrameCallback(_postFrameCallback); } // Index value which identify javascript runtime context. @@ -681,12 +690,13 @@ class WebFViewController implements WidgetsBindingObserver, ElementsBindingObser } // Call from JS Bridge before JS side eventTarget object been Garbage collected. - void disposeEventTarget(int targetId) { + void disposeEventTarget(int targetId, Pointer pointer) { Node? target = _getEventTargetById(targetId); if (target == null) return; _removeTarget(targetId); target.dispose(); + malloc.free(pointer); } RenderObject getRootRenderObject() { @@ -968,7 +978,8 @@ class WebFController { _module.dispose(); _view.dispose(); - allocateNewPage(_view.contextId); + List methodBytes = makeDartMethodsData(); + allocateNewPage(methodBytes, _view.contextId); _view = WebFViewController(view.viewportWidth, view.viewportHeight, background: _view.background, diff --git a/webf/lib/src/launcher/launcher.dart b/webf/lib/src/launcher/launcher.dart index 8f799e2f9f..00f316515b 100644 --- a/webf/lib/src/launcher/launcher.dart +++ b/webf/lib/src/launcher/launcher.dart @@ -39,11 +39,8 @@ void launch( VoidCallback? _ordinaryOnMetricsChanged = window.onMetricsChanged; Future _initWebFApp() async { - WebFNativeChannel channel = WebFNativeChannel(); - if (bundle == null) { String? backendEntrypointUrl = getBundleURLFromEnv() ?? getBundlePathFromEnv(); - backendEntrypointUrl ??= await channel.getUrl(); if (backendEntrypointUrl != null) { bundle = WebFBundle.fromUrl(backendEntrypointUrl); } @@ -55,7 +52,6 @@ void launch( window.physicalSize.height / window.devicePixelRatio, background: background, showPerformanceOverlay: showPerformanceOverlay ?? Platform.environment[ENABLE_PERFORMANCE_OVERLAY] != null, - methodChannel: channel, entrypoint: bundle, devToolsService: devToolsService, httpClientInterceptor: httpClientInterceptor, diff --git a/webf/lib/src/module/method_channel.dart b/webf/lib/src/module/method_channel.dart index d314991f5e..4af794e326 100644 --- a/webf/lib/src/module/method_channel.dart +++ b/webf/lib/src/module/method_channel.dart @@ -5,7 +5,6 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; import 'package:webf/webf.dart'; typedef MethodCallCallback = Future Function(String method, Object? arguments); @@ -22,7 +21,7 @@ class MethodChannelModule extends BaseModule { void dispose() {} @override - String invoke(String method, params, callback) { + dynamic invoke(String method, params, callback) { if (method == 'invokeMethod') { _invokeMethodFromJavaScript(moduleManager!.controller, params[0], params[1]).then((result) { callback(data: result); @@ -34,18 +33,6 @@ class MethodChannelModule extends BaseModule { } } -void setJSMethodCallCallback(WebFController controller) { - if (controller.methodChannel == null) return; - - controller.methodChannel!._onJSMethodCall = (String method, arguments) async { - try { - controller.module.moduleManager.emitModuleEvent(METHOD_CHANNEL_NAME, data: [method, arguments]); - } catch (e, stack) { - print('Error invoke module event: $e, $stack'); - } - }; -} - abstract class WebFMethodChannel { MethodCallCallback? _onJSMethodCallCallback; @@ -58,7 +45,11 @@ abstract class WebFMethodChannel { static void setJSMethodCallCallback(WebFController controller) { controller.methodChannel?._onJSMethodCall = (String method, arguments) async { - controller.module.moduleManager.emitModuleEvent(METHOD_CHANNEL_NAME, data: [method, arguments]); + try { + return controller.module.moduleManager.emitModuleEvent(METHOD_CHANNEL_NAME, data: [method, arguments]); + } catch (e, stack) { + print('Error invoke module event: $e, $stack'); + } }; } } @@ -93,54 +84,6 @@ class WebFJavaScriptChannel extends WebFMethodChannel { } } -class WebFNativeChannel extends WebFMethodChannel { - // Flutter method channel used to communicate with public SDK API - // Only works when integration wieh public SDK API - - static final MethodChannel _nativeChannel = getWebFMethodChannel() - ..setMethodCallHandler((call) async { - String method = call.method; - WebFController? controller = WebFController.getControllerOfJSContextId(0); - - if (controller == null) return; - - if ('reload' == method) { - await controller.reload(); - } else if (controller.methodChannel!._onJSMethodCallCallback != null) { - return controller.methodChannel!._onJSMethodCallCallback!(method, call.arguments); - } - - return Future.value(null); - }); - - @override - Future invokeMethodFromJavaScript(String method, List arguments) async { - Map argsWrap = { - 'method': method, - 'args': arguments, - }; - return _nativeChannel.invokeMethod('invokeMethod', argsWrap); - } - - Future getUrl() async { - // Maybe url of zip bundle or js bundle - String? url = await _nativeChannel.invokeMethod('getUrl'); - - // @NOTE(zhuoling.lcl): Android plugin protocol cannot return `null` directly, which - // will case method channel invoke failed with exception, use empty - // string to represent null value. - if (url != null && url.isEmpty) url = null; - return url; - } - - static Future syncDynamicLibraryPath() async { - String? path = await _nativeChannel.invokeMethod('getDynamicLibraryPath'); - if (path != null) { - WebFDynamicLibrary.dynamicLibraryPath = path; - } - } -} - Future _invokeMethodFromJavaScript(WebFController? controller, String method, List args) { WebFMethodChannel? webFMethodChannel = controller?.methodChannel; if (webFMethodChannel != null) { diff --git a/webf/lib/src/module/module_manager.dart b/webf/lib/src/module/module_manager.dart index f1ad8ec966..707f9a2c80 100644 --- a/webf/lib/src/module/module_manager.dart +++ b/webf/lib/src/module/module_manager.dart @@ -2,8 +2,6 @@ * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import 'dart:convert'; - import 'package:webf/bridge.dart' as bridge; import 'package:webf/dom.dart'; import 'package:webf/webf.dart'; @@ -12,11 +10,14 @@ abstract class BaseModule { String get name; final ModuleManager? moduleManager; BaseModule(this.moduleManager); - String invoke(String method, params, InvokeModuleCallback callback); + dynamic invoke(String method, params, InvokeModuleCallback callback); + dynamic dispatchEvent({Event? event, data}) { + return moduleManager!.emitModuleEvent(name, event: event, data: data); + } void dispose(); } -typedef InvokeModuleCallback = void Function({String? error, Object? data}); +typedef InvokeModuleCallback = Future Function({String? error, Object? data}); typedef NewModuleCreator = BaseModule Function(ModuleManager); typedef ModuleCreator = BaseModule Function(ModuleManager? moduleManager); @@ -61,11 +62,11 @@ class ModuleManager { _creatorMap[fakeModule.name] = moduleCreator; } - void emitModuleEvent(String moduleName, {Event? event, Object? data}) { - bridge.emitModuleEvent(contextId, moduleName, event, jsonEncode(data)); + dynamic emitModuleEvent(String moduleName, {Event? event, data}) { + return bridge.emitModuleEvent(contextId, moduleName, event, data); } - String invokeModule(String moduleName, String method, params, InvokeModuleCallback callback) { + dynamic invokeModule(String moduleName, String method, params, InvokeModuleCallback callback) { ModuleCreator? creator = _creatorMap[moduleName]; if (creator == null) { throw Exception('ModuleManager: Can not find module of name: $moduleName'); diff --git a/webf/lib/src/widget/webf.dart b/webf/lib/src/widget/webf.dart index 70dfe16bbc..a6a9828c35 100644 --- a/webf/lib/src/widget/webf.dart +++ b/webf/lib/src/widget/webf.dart @@ -199,11 +199,6 @@ class WebFRenderObjectWidget extends SingleChildRenderObjectWidget { double viewportWidth = _webfWidget.viewportWidth ?? window.physicalSize.width / window.devicePixelRatio; double viewportHeight = _webfWidget.viewportHeight ?? window.physicalSize.height / window.devicePixelRatio; - if (viewportWidth == 0.0 && viewportHeight == 0.0) { - throw FlutterError('''Can't get viewportSize from window. Please set viewportWidth and viewportHeight manually. -This situation often happened when you trying creating webf when FlutterView not initialized.'''); - } - WebFController controller = WebFController(shortHash(_webfWidget.hashCode), viewportWidth, viewportHeight, background: _webfWidget.background, showPerformanceOverlay: Platform.environment[ENABLE_PERFORMANCE_OVERLAY] != null, @@ -226,6 +221,33 @@ This situation often happened when you trying creating webf when FlutterView not onControllerCreated(controller); } + if (viewportWidth == 0.0 && viewportHeight == 0.0) { + // window.physicalSize are Size.zero when app first loaded. This only happened on Android and iOS physical devices with release build. + // We should wait for onMetricsChanged when window.physicalSize get updated from Flutter Engine. + VoidCallback? _ordinaryOnMetricsChanged = window.onMetricsChanged; + window.onMetricsChanged = () async { + if (window.physicalSize == Size.zero) { + return; + } + + double viewportWidth = _webfWidget.viewportWidth ?? window.physicalSize.width / window.devicePixelRatio; + double viewportHeight = _webfWidget.viewportHeight ?? window.physicalSize.height / window.devicePixelRatio; + + controller.view.viewportWidth = viewportWidth; + controller.view.document.documentElement!.renderStyle.width = CSSLengthValue(viewportWidth, CSSLengthType.PX); + + controller.view.viewportHeight = viewportHeight; + controller.view.document.documentElement!.renderStyle.height = CSSLengthValue(viewportHeight, CSSLengthType.PX); + + // Should proxy to ordinary window.onMetricsChanged callbacks. + if (_ordinaryOnMetricsChanged != null) { + _ordinaryOnMetricsChanged(); + // Recover ordinary callback to window.onMetricsChanged + window.onMetricsChanged = _ordinaryOnMetricsChanged; + } + }; + } + if (kProfileMode) { PerformanceTiming.instance().mark(PERF_CONTROLLER_INIT_END); } diff --git a/webf/pubspec.yaml b/webf/pubspec.yaml index f7018c58c0..524ca88608 100644 --- a/webf/pubspec.yaml +++ b/webf/pubspec.yaml @@ -1,11 +1,11 @@ name: webf description: A W3C standard compliant Web rendering engine based on Flutter. -version: 0.12.0+2 +version: 0.13.0-beta.3 homepage: https://openwebf.com environment: sdk: ">=2.17.5 <3.0.0" - flutter: ">=3.0.2 <=3.0.5" + flutter: ">=3.0.5" dependencies: flutter: @@ -14,6 +14,7 @@ dependencies: meta: ^1.7.0 # Pure dart module. ffi: ^2.0.1 # Pure dart module. characters: ^1.2.0 + collection: ^1.16.0 async: ^2.8.2 # Pure dart module. quiver: ^3.1.0 # Pure dart module. vector_math: ^2.1.2 # Pure dart module.