diff --git a/bridge/.clang-format b/bridge/.clang-format index 7a3401a6ad..435f5be99b 100644 --- a/bridge/.clang-format +++ b/bridge/.clang-format @@ -6,4 +6,4 @@ BasedOnStyle: Chromium # 'vector>'. ('Auto' means that clang-format will only use # 'int>>' if the file already contains at least one such instance.) Standard: c++17 -ColumnLimit: 200 +ColumnLimit: 120 diff --git a/bridge/.gitignore b/bridge/.gitignore index e997ce2614..2f874e24f5 100644 --- a/bridge/.gitignore +++ b/bridge/.gitignore @@ -2,3 +2,4 @@ xcschememanagement.plist cmake-build-* build +out diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index e25b92ee62..9094fc889c 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.10.0) -set(CMAKE_OSX_DEPLOYMENT_TARGET 10.11) project(KrakenBridge) +set(CMAKE_OSX_DEPLOYMENT_TARGET 10.11) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -34,15 +34,10 @@ execute_process( ) # g execute_process( - COMMAND bash "-c" "node bin/code_generator -s ../../bindings/qjs/dom/elements -d ../../bindings/qjs/dom/elements/.gen" + COMMAND bash "-c" "node bin/code_generator -s ../../core -d ../../out" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/scripts/code_generator ) # generate elements code -execute_process( - COMMAND bash "-c" "node bin/code_generator -s ../../bindings/qjs/dom/events -d ../../bindings/qjs/dom/events/.gen" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/scripts/code_generator -) # generate events code - execute_process( COMMAND bash "-c" "read dart_sdk < <(type -p dart) && echo $\{dart_sdk%/*\}/cache/dart-sdk/include | xargs" OUTPUT_VARIABLE DART_SDK @@ -83,11 +78,12 @@ endif() list(APPEND BRIDGE_SOURCE kraken_bridge.cc ${CMAKE_CURRENT_SOURCE_DIR}/include/kraken_bridge.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/kraken_foundation.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/dart_methods.h + foundation/macros.h foundation/logging.cc foundation/logging.h foundation/colors.h + foundation/native_string.cc + foundation/native_string.h foundation/ref_counted_internal.h foundation/ref_counter.h foundation/ref_ptr.h @@ -98,11 +94,16 @@ list(APPEND BRIDGE_SOURCE foundation/inspector_task_queue.cc foundation/task_queue.cc foundation/task_queue.h + foundation/string_view.cc + foundation/string_view.h + foundation/native_value.cc + foundation/native_value.h + foundation/native_type.h + foundation/native_value_converter.h + foundation/native_value_converter.cc + foundation/casting.h foundation/ui_command_buffer.cc foundation/ui_command_buffer.h - foundation/ui_command_callback_queue.cc - foundation/closure.h - dart_methods.cc polyfill/dist/polyfill.cc ) @@ -142,6 +143,7 @@ list(APPEND GUMBO_PARSER list(APPEND BRIDGE_INCLUDE ${CMAKE_CURRENT_LIST_DIR}/foundation + ${CMAKE_CURRENT_LIST_DIR}/out ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/include ${CMAKE_CURRENT_LIST_DIR}/polyfill/dist @@ -186,106 +188,295 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") list(APPEND BRIDGE_LINK_LIBS quickjs) list(APPEND BRIDGE_SOURCE - page.cc - page.h - bindings/qjs/garbage_collected.h - bindings/qjs/executing_context.cc - bindings/qjs/executing_context.h + # Binding files + bindings/qjs/idl_type.h + bindings/qjs/converter.h + bindings/qjs/converter_impl.h + bindings/qjs/dictionary_base.cc + bindings/qjs/dictionary_base.h + bindings/qjs/js_based_event_listener.cc + bindings/qjs/js_based_event_listener.h + bindings/qjs/js_event_handler.cc + bindings/qjs/js_event_handler.h + bindings/qjs/js_event_listener.cc + bindings/qjs/js_event_listener.h + bindings/qjs/binding_initializer.cc + bindings/qjs/binding_initializer.h + bindings/qjs/member_installer.cc + bindings/qjs/member_installer.h + bindings/qjs/source_location.cc + bindings/qjs/source_location.h + bindings/qjs/cppgc/garbage_collected.h + bindings/qjs/cppgc/gc_visitor.cc + bindings/qjs/cppgc/gc_visitor.h + bindings/qjs/cppgc/mutation_scope.cc + bindings/qjs/cppgc/mutation_scope.h + bindings/qjs/cppgc/member.h + bindings/qjs/cppgc/local_handle.h + bindings/qjs/script_wrappable.cc + bindings/qjs/script_wrappable.h + bindings/qjs/wrapper_type_info.h bindings/qjs/heap_hashmap.h - bindings/qjs/native_value.cc - bindings/qjs/native_value.h - bindings/qjs/host_object.h - bindings/qjs/host_object.cc - bindings/qjs/host_class.h - bindings/qjs/qjs_patch.cc - bindings/qjs/qjs_patch.h - bindings/qjs/rejected_promises.cc + bindings/qjs/native_string_utils.cc + bindings/qjs/native_string_utils.h + bindings/qjs/qjs_engine_patch.cc + bindings/qjs/qjs_engine_patch.h + bindings/qjs/qjs_function.cc + bindings/qjs/qjs_function.h + bindings/qjs/script_value.cc + bindings/qjs/script_value.h + bindings/qjs/script_promise.cc + bindings/qjs/script_promise.h + bindings/qjs/to_quickjs.h + bindings/qjs/qjs_interface_bridge.cc + bindings/qjs/qjs_interface_bridge.h + bindings/qjs/script_promise_resolver.cc + bindings/qjs/script_promise_resolver.h + bindings/qjs/atomic_string.cc + bindings/qjs/atomic_string.h + bindings/qjs/generated_code_helper.h + bindings/qjs/exception_state.cc + bindings/qjs/exception_state.h + bindings/qjs/exception_message.cc + bindings/qjs/exception_message.h bindings/qjs/rejected_promises.h - bindings/qjs/module_manager.cc - bindings/qjs/module_manager.h - bindings/qjs/html_parser.cc - bindings/qjs/html_parser.h - bindings/qjs/bom/console.cc - bindings/qjs/bom/console.h - bindings/qjs/bom/screen.cc - bindings/qjs/bom/screen.h - bindings/qjs/bom/timer.cc - bindings/qjs/bom/timer.h - bindings/qjs/bom/dom_timer_coordinator.cc - bindings/qjs/bom/dom_timer_coordinator.h - bindings/qjs/dom/frame_request_callback_collection.cc - bindings/qjs/dom/frame_request_callback_collection.h - bindings/qjs/dom/event_listener_map.cc - bindings/qjs/dom/event_listener_map.h - bindings/qjs/dom/script_animation_controller.cc - bindings/qjs/dom/script_animation_controller.h - bindings/qjs/dom/event_target.cc - bindings/qjs/dom/event_target.h - bindings/qjs/dom/event.cc - bindings/qjs/dom/event.h - bindings/qjs/dom/node.h - bindings/qjs/dom/node.cc - bindings/qjs/dom/element.cc - bindings/qjs/dom/element.h - bindings/qjs/dom/document.cc - bindings/qjs/dom/document.h - bindings/qjs/dom/text_node.cc - bindings/qjs/dom/text_node.h - bindings/qjs/dom/event_type_names.h - bindings/qjs/dom/event_type_names.cc - bindings/qjs/dom/comment_node.cc - bindings/qjs/dom/comment_node.h - bindings/qjs/dom/document_fragment.cc - bindings/qjs/dom/document_fragment.h - bindings/qjs/dom/style_declaration.cc - bindings/qjs/dom/style_declaration.h - bindings/qjs/dom/css_property_list.h - bindings/qjs/dom/elements/.gen/canvas_element.cc - bindings/qjs/dom/elements/.gen/canvas_element.h - bindings/qjs/dom/elements/image_element.cc - bindings/qjs/dom/elements/image_element.h - bindings/qjs/dom/elements/.gen/input_element.cc - bindings/qjs/dom/elements/.gen/input_element.h - bindings/qjs/dom/elements/.gen/textarea_element.cc - bindings/qjs/dom/elements/.gen/textarea_element.h - bindings/qjs/dom/elements/.gen/anchor_element.cc - bindings/qjs/dom/elements/.gen/anchor_element.h - bindings/qjs/dom/elements/.gen/object_element.cc - bindings/qjs/dom/elements/.gen/object_element.h - bindings/qjs/dom/elements/.gen/script_element.cc - bindings/qjs/dom/elements/.gen/script_element.h - bindings/qjs/dom/elements/template_element.cc - bindings/qjs/dom/elements/template_element.h - bindings/qjs/dom/events/.gen/close_event.h - bindings/qjs/dom/events/.gen/close_event.cc - bindings/qjs/dom/events/.gen/gesture_event.cc - bindings/qjs/dom/events/.gen/gesture_event.h - bindings/qjs/dom/events/.gen/input_event.cc - bindings/qjs/dom/events/.gen/input_event.h - bindings/qjs/dom/events/.gen/popstate_event.cc - bindings/qjs/dom/events/.gen/popstate_event.h - bindings/qjs/dom/events/.gen/intersection_change.cc - bindings/qjs/dom/events/.gen/intersection_change.h - bindings/qjs/dom/events/.gen/media_error_event.cc - bindings/qjs/dom/events/.gen/media_error_event.h - bindings/qjs/dom/events/.gen/mouse_event.cc - bindings/qjs/dom/events/.gen/mouse_event.h - bindings/qjs/dom/events/.gen/message_event.h - bindings/qjs/dom/events/.gen/message_event.cc - bindings/qjs/dom/events/touch_event.cc - bindings/qjs/dom/events/touch_event.h - bindings/qjs/bom/blob.cc - bindings/qjs/bom/blob.h - bindings/qjs/bom/location.h - bindings/qjs/bom/location.cc - bindings/qjs/bom/window.cc - bindings/qjs/bom/window.h - bindings/qjs/bom/performance.cc - bindings/qjs/bom/performance.h - bindings/qjs/dom/custom_event.cc - bindings/qjs/dom/custom_event.h - bindings/qjs/dom/all_collection.cc - bindings/qjs/dom/all_collection.h + bindings/qjs/rejected_promises.cc + bindings/qjs/pending_promises.cc + bindings/qjs/pending_promises.h + + # Core sources + core/executing_context.cc + core/executing_context.h + core/script_state.cc + core/script_state.h + core/page.h + core/page.cc + core/executing_context_data.cc + core/executing_context_data.h + core/dart_methods.h + core/dart_methods.h + core/fileapi/blob.h + core/fileapi/blob.cc + core/fileapi/blob_part.cc + core/fileapi/blob_part.h + core/fileapi/blob_property_bag.cc + core/fileapi/blob_property_bag.h + core/frame/console.cc + core/frame/console.h + core/frame/dom_timer.cc + core/frame/dom_timer.h + core/frame/dom_timer_coordinator.cc + core/frame/dom_timer_coordinator.h + core/frame/window_or_worker_global_scope.cc + core/frame/window_or_worker_global_scope.h + core/frame/module_listener.cc + core/frame/module_listener.h + core/frame/module_listener_container.cc + core/frame/module_listener_container.h + core/frame/module_manager.cc + core/frame/module_manager.h + core/frame/module_callback.cc + core/frame/module_callback.h + core/frame/module_callback_coordinator.cc + core/frame/window.h + core/frame/window.cc + core/frame/screen.h + core/frame/screen.cc + core/frame/legacy/location.cc + core/frame/legacy/location.h + core/frame/module_callback_coordinator.h + core/css/legacy/css_style_declaration.cc + core/css/legacy/css_style_declaration.h + core/dom/frame_request_callback_collection.cc + core/dom/frame_request_callback_collection.h + core/dom/events/event_listener.h + core/dom/events/registered_eventListener.cc + core/dom/events/registered_eventListener.h + core/dom/events/event_listener_map.cc + core/dom/events/event_listener_map.h + core/dom/events/event.h + core/dom/events/event.cc + core/dom/events/event_target.h + core/dom/events/event_target.cc + core/dom/events/event_listener_map.cc + core/dom/events/event_target_impl.cc + core/dom/events/event_target_impl.h + core/dom/binding_object.h + core/dom/binding_object.cc + core/dom/node.cc + core/dom/node.h + core/dom/node_traversal.cc + core/dom/node_traversal.h + core/dom/character_data.cc + core/dom/character_data.h + core/dom/comment.cc + core/dom/comment.h + core/dom/text.cc + core/dom/text.h + core/dom/tree_scope.cc + core/dom/tree_scope.h + core/dom/element.cc + core/dom/element.h + core/dom/element_traversal.h + core/dom/document.cc + core/dom/document.h + core/dom/scripted_animation_controller.cc + core/dom/scripted_animation_controller.h + core/dom/node_data.cc + core/dom/node_data.h + core/dom/document_fragment.h + core/dom/document_fragment.cc + core/dom/collection_index_cache.h + core/dom/child_node_list.cc + core/dom/child_node_list.h + core/dom/empty_node_list.cc + core/dom/empty_node_list.h + core/dom/node_list.h + core/dom/container_node.cc + core/dom/container_node.h + core/events/error_event.cc + core/events/error_event.h + core/events/message_event.h + core/events/message_event.cc + core/html/parser/html_parser.cc + core/html/parser/html_parser.h + core/html/html_collection.cc + core/html/html_collection.h + core/html/html_element.cc + core/html/html_element.h + core/html/html_div_element.cc + core/html/html_div_element.h + core/html/html_head_element.cc + core/html/html_head_element.h + core/html/html_body_element.h + core/html/html_body_element.cc + core/html/html_html_element.cc + core/html/html_html_element.h + core/html/html_template_element.cc + core/html/html_template_element.h +# core/html/html_anchor_element.h +# core/html/html_anchor_element.cc +# core/html/html_template_element.cc +# core/html/html_template_element.h +# core/html/forms/html_input_element.cc +# core/html/forms/html_input_element.h +# core/html/forms/html_textarea_element.cc +# core/html/forms/html_textarea_element.h +# core/html/html_image_element.cc +# core/html/html_image_element.h +# core/html/html_script_element.cc +# core/html/html_script_element.h + core/html/html_unknown_element.cc + core/html/html_unknown_element.h + # Legacy implements, should remove them in the future. + core/dom/legacy/space_split_string.cc + core/dom/legacy/space_split_string.h + core/dom/legacy/element_attributes.cc + core/dom/legacy/element_attributes.h + core/dom/legacy/bounding_client_rect.cc + core/dom/legacy/bounding_client_rect.h + +# core/dom/character_data.cc +# core/dom/character_data.h +# core/dom/comment.cc +# core/dom/comment.h +# core/dom/node.cc +# core/dom/node.h +# core/dom/events/custom_event.cc +# core/dom/events/custom_event.h +# core/dom/events/event.h +# core/dom/events/event.cc +# core/dom/events/event_listener_map.cc +# core/dom/events/event_listener_map.h +# core/dom/events/event_target.cc +# core/dom/events/event_target.h + ) + + # Gen sources. + list(APPEND BRIDGE_SOURCE + out/qjs_console.cc + out/qjs_console.h + out/qjs_module_manager.cc + out/qjs_module_manager.h + out/qjs_window_or_worker_global_scope.cc + out/qjs_window_or_worker_global_scope.h + out/qjs_window.cc + out/qjs_window.h + out/qjs_location.cc + out/qjs_location.h + out/qjs_blob.cc + out/qjs_blob.h + out/qjs_event.cc + out/qjs_event.h + out/qjs_add_event_listener_options.cc + out/qjs_add_event_listener_options.h + out/qjs_event_listener_options.cc + out/qjs_event_listener_options.h + out/qjs_error_event.h + out/qjs_error_event.cc + out/qjs_message_event.cc + out/qjs_message_event.h + out/qjs_message_event_init.h + out/qjs_message_event_init.cc + out/qjs_error_event_init.h + out/qjs_error_event_init.cc + out/qjs_event_init.h + out/qjs_event_init.cc + out/qjs_event_target.cc + out/qjs_event_target.h + out/qjs_node.h + out/qjs_node.cc + out/qjs_document.cc + out/qjs_document.h + out/qjs_element.cc + out/qjs_element.h + out/qjs_element_attributes.cc + out/qjs_element_attributes.h + out/qjs_character_data.cc + out/qjs_character_data.h + out/qjs_comment.cc + out/qjs_comment.h + out/qjs_document_fragment.cc + out/qjs_document_fragment.h + out/qjs_bounding_client_rect.cc + out/qjs_bounding_client_rect.h + out/qjs_css_style_declaration.cc + out/qjs_css_style_declaration.h + out/qjs_text.cc + out/qjs_text.h + out/qjs_screen.cc + out/qjs_screen.h + out/qjs_node_list.cc + out/qjs_node_list.h + out/event_type_names.h + out/event_type_names.cc + out/built_in_string.cc + out/built_in_string.h + out/binding_call_methods.cc + out/binding_call_methods.h + out/qjs_scroll_options.cc + out/qjs_scroll_options.h + out/qjs_scroll_to_options.cc + out/qjs_scroll_to_options.h + out/qjs_html_element.cc + out/qjs_html_element.h + out/qjs_html_div_element.cc + out/qjs_html_div_element.h + out/qjs_html_head_element.cc + out/qjs_html_head_element.h + out/qjs_html_body_element.cc + out/qjs_html_body_element.h + out/qjs_html_html_element.cc + out/qjs_html_html_element.h + out/qjs_html_template_element.cc + out/qjs_html_template_element.h + out/html_element_type_helper.h + out/qjs_html_unknown_element.cc + out/qjs_html_unknown_element.h + out/html_element_factory.cc + out/html_element_factory.h + out/html_names.cc + out/html_names.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 @@ -297,7 +488,7 @@ endif () list(APPEND PUBLIC_HEADER include/kraken_bridge.h - ) +) add_library(kraken SHARED ${BRIDGE_SOURCE}) add_library(kraken_static STATIC ${BRIDGE_SOURCE}) diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc new file mode 100644 index 0000000000..dc24878101 --- /dev/null +++ b/bridge/bindings/qjs/atomic_string.cc @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "atomic_string.h" +#include "built_in_string.h" + +namespace kraken { + +AtomicString AtomicString::Empty(JSContext* ctx) { + AtomicString tmp = built_in_string::kempty_string; + return tmp; +} + +AtomicString AtomicString::From(JSContext* ctx, NativeString* native_string) { + JSValue str = JS_NewUnicodeString(ctx, native_string->string(), native_string->length()); + auto result = AtomicString(ctx, str); + JS_FreeValue(ctx, str); + return result; +} + +namespace { + +AtomicString::StringKind GetStringKind(const std::string& string) { + AtomicString::StringKind predictKind = + std::islower(string[0]) ? AtomicString::StringKind::kIsLowerCase : AtomicString::StringKind::kIsUpperCase; + for (char i : string) { + if (predictKind == AtomicString::StringKind::kIsUpperCase && !std::isupper(i)) { + return AtomicString::StringKind::kIsMixed; + } else if (predictKind == AtomicString::StringKind::kIsLowerCase && !std::islower(i)) { + return AtomicString::StringKind::kIsMixed; + } + } + return predictKind; +} + +AtomicString::StringKind GetStringKind(JSValue stringValue) { + JSString* p = JS_VALUE_GET_STRING(stringValue); + + if (p->is_wide_char) { + return AtomicString::StringKind::kIsMixed; + } + + return GetStringKind(reinterpret_cast(p->u.str8)); +} + +} // namespace + +AtomicString::AtomicString(JSContext* ctx, const std::string& string) + : runtime_(JS_GetRuntime(ctx)), + ctx_(ctx), + atom_(JS_NewAtom(ctx, string.c_str())), + kind_(GetStringKind(string)), + length_(string.size()) {} + +AtomicString::AtomicString(JSContext* ctx, JSValue value) + : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)) { + if (JS_IsString(value)) { + kind_ = GetStringKind(value); + length_ = JS_VALUE_GET_STRING(value)->len; + } +} + +AtomicString::AtomicString(JSContext* ctx, JSAtom atom) + : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_DupAtom(ctx, atom)) { + JSValue string = JS_AtomToValue(ctx, atom); + kind_ = GetStringKind(string); + length_ = JS_VALUE_GET_STRING(string)->len; + JS_FreeValue(ctx, string); +} + +bool AtomicString::IsNull() const { + return atom_ == JS_ATOM_NULL; +} + +bool AtomicString::IsEmpty() const { + return *this == built_in_string::kempty_string; +} + +std::string AtomicString::ToStdString() const { + const char* buf = JS_AtomToCString(ctx_, atom_); + std::string result = std::string(buf); + JS_FreeCString(ctx_, buf); + return result; +} + +std::unique_ptr AtomicString::ToNativeString() const { + JSValue stringValue = JS_AtomToValue(ctx_, atom_); + uint32_t length; + uint16_t* bytes = JS_ToUnicode(ctx_, stringValue, &length); + JS_FreeValue(ctx_, stringValue); + return std::make_unique(bytes, length); +} + +StringView AtomicString::ToStringView() const { + JSValue stringValue = JS_AtomToValue(ctx_, atom_); + JSString* string = JS_VALUE_GET_STRING(stringValue); + assert(string->header.ref_count > 1); + JS_FreeValue(ctx_, stringValue); + return StringView(string->u.str8, string->len, string->is_wide_char); +} + +AtomicString::AtomicString(const AtomicString& value) { + if (&value != this) { + atom_ = JS_DupAtom(value.ctx_, value.atom_); + } + ctx_ = value.ctx_; + runtime_ = value.runtime_; + length_ = value.length_; + kind_ = value.kind_; +} + +AtomicString& AtomicString::operator=(const AtomicString& other) { + if (&other != this) { + atom_ = JS_DupAtom(other.ctx_, other.atom_); + } + runtime_ = other.runtime_; + ctx_ = other.ctx_; + length_ = other.length_; + kind_ = other.kind_; + return *this; +} + +AtomicString::AtomicString(AtomicString&& value) noexcept { + if (&value != this) { + atom_ = JS_DupAtom(value.ctx_, value.atom_); + } + ctx_ = value.ctx_; + runtime_ = value.runtime_; + length_ = value.length_; + kind_ = value.kind_; +} + +AtomicString& AtomicString::operator=(AtomicString&& value) noexcept { + if (&value != this) { + atom_ = JS_DupAtom(value.ctx_, value.atom_); + } + ctx_ = value.ctx_; + runtime_ = value.runtime_; + length_ = value.length_; + kind_ = value.kind_; + return *this; +} + +AtomicString AtomicString::ToUpperIfNecessary() const { + if (kind_ == StringKind::kIsUpperCase) { + return *this; + } + if (atom_upper_ != JS_ATOM_NULL) + return *this; + AtomicString upperString = ToUpperSlow(); + atom_upper_ = upperString.atom_; + return upperString; +} + +const AtomicString AtomicString::ToUpperSlow() const { + const char* cptr = JS_AtomToCString(ctx_, atom_); + std::string str = std::string(cptr); + std::transform(str.begin(), str.end(), str.begin(), toupper); + JS_FreeCString(ctx_, cptr); + return AtomicString(ctx_, str); +} + +const AtomicString AtomicString::ToLowerIfNecessary() const { + if (kind_ == StringKind::kIsLowerCase) { + return *this; + } + if (atom_lower_ != JS_ATOM_NULL) + return *this; + AtomicString lowerString = ToLowerSlow(); + atom_lower_ = lowerString.atom_; + return lowerString; +} + +const AtomicString AtomicString::ToLowerSlow() const { + const char* cptr = JS_AtomToCString(ctx_, atom_); + std::string str = std::string(cptr); + std::transform(str.begin(), str.end(), str.begin(), tolower); + JS_FreeCString(ctx_, cptr); + return AtomicString(ctx_, str); +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h new file mode 100644 index 0000000000..4e213b292e --- /dev/null +++ b/bridge/bindings/qjs/atomic_string.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ + +#include +#include +#include +#include "foundation/macros.h" +#include "foundation/native_string.h" +#include "foundation/string_view.h" +#include "native_string_utils.h" +#include "qjs_engine_patch.h" + +namespace kraken { + +// An AtomicString instance represents a string, and multiple AtomicString +// instances can share their string storage if the strings are +// identical. Comparing two AtomicString instances is much faster than comparing +// two String instances because we just check string storage identity. +class AtomicString { + KRAKEN_DISALLOW_NEW(); + + public: + enum class StringKind { kIsLowerCase, kIsUpperCase, kIsMixed }; + + struct KeyHasher { + std::size_t operator()(const AtomicString& k) const { return k.atom_; } + }; + + static AtomicString Empty(JSContext* ctx); + static AtomicString From(JSContext* ctx, NativeString* native_string); + + AtomicString() = default; + AtomicString(JSContext* ctx, const std::string& string); + AtomicString(JSContext* ctx, JSValue value); + AtomicString(JSContext* ctx, JSAtom atom); + ~AtomicString() { JS_FreeAtomRT(runtime_, atom_); }; + + // Return the undefined string value from atom key. + JSValue ToQuickJS(JSContext* ctx) const { + if (ctx_ == nullptr) { + return JS_NULL; + } + + assert(ctx_ != nullptr); + return JS_AtomToValue(ctx, atom_); + }; + + bool IsNull() const; + bool IsEmpty() const; + + JSAtom Impl() const { return atom_; } + + int64_t length() const { return length_; } + + [[nodiscard]] std::string ToStdString() const; + [[nodiscard]] std::unique_ptr ToNativeString() const; + + StringView ToStringView() const; + + AtomicString ToUpperIfNecessary() const; + const AtomicString ToUpperSlow() const; + + const AtomicString ToLowerIfNecessary() const; + const AtomicString ToLowerSlow() const; + + // Copy assignment + AtomicString(AtomicString const& value); + AtomicString& operator=(const AtomicString& other); + + // Move assignment + AtomicString(AtomicString&& value) noexcept; + AtomicString& operator=(AtomicString&& value) noexcept; + + bool operator==(const AtomicString& other) const { return other.atom_ == this->atom_; } + bool operator!=(const AtomicString& other) const { return other.atom_ != this->atom_; }; + + protected: + JSContext* ctx_{nullptr}; + JSRuntime* runtime_{nullptr}; + int64_t length_{0}; + JSAtom atom_{JS_ATOM_NULL}; + mutable JSAtom atom_upper_{JS_ATOM_NULL}; + mutable JSAtom atom_lower_{JS_ATOM_NULL}; + StringKind kind_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ diff --git a/bridge/bindings/qjs/atomic_string_test.cc b/bridge/bindings/qjs/atomic_string_test.cc new file mode 100644 index 0000000000..e9ba3e389a --- /dev/null +++ b/bridge/bindings/qjs/atomic_string_test.cc @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "atomic_string.h" +#include +#include +#include "built_in_string.h" +#include "event_type_names.h" +#include "gtest/gtest.h" +#include "native_string_utils.h" +#include "qjs_engine_patch.h" + +using namespace kraken; + +using TestCallback = void (*)(JSContext* ctx); + +void TestAtomicString(TestCallback callback) { + JSRuntime* runtime = JS_NewRuntime(); + JSContext* ctx = JS_NewContext(runtime); + + built_in_string::Init(ctx); + + callback(ctx); + + JS_FreeContext(ctx); + + built_in_string::Dispose(); + JS_FreeRuntime(runtime); +} + +TEST(AtomicString, Empty) { + TestAtomicString([](JSContext* ctx) { + AtomicString atomic_string = AtomicString::Empty(ctx); + EXPECT_STREQ(atomic_string.ToStdString().c_str(), ""); + }); +} + +TEST(AtomicString, FromNativeString) { + TestAtomicString([](JSContext* ctx) { + auto nativeString = stringToNativeString("helloworld"); + AtomicString value = AtomicString::From(ctx, nativeString.get()); + + EXPECT_STREQ(value.ToStdString().c_str(), "helloworld"); + }); +} + +TEST(AtomicString, CreateFromStdString) { + TestAtomicString([](JSContext* ctx) { + AtomicString&& value = AtomicString(ctx, "helloworld"); + EXPECT_STREQ(value.ToStdString().c_str(), "helloworld"); + }); +} + +TEST(AtomicString, CreateFromJSValue) { + TestAtomicString([](JSContext* ctx) { + JSValue string = JS_NewString(ctx, "helloworld"); + AtomicString&& value = AtomicString(ctx, string); + EXPECT_STREQ(value.ToStdString().c_str(), "helloworld"); + JS_FreeValue(ctx, string); + }); +} + +TEST(AtomicString, ToQuickJS) { + TestAtomicString([](JSContext* ctx) { + AtomicString&& value = AtomicString(ctx, "helloworld"); + JSValue qjs_value = value.ToQuickJS(ctx); + const char* buffer = JS_ToCString(ctx, qjs_value); + EXPECT_STREQ(buffer, "helloworld"); + JS_FreeValue(ctx, qjs_value); + JS_FreeCString(ctx, buffer); + }); +} + +TEST(AtomicString, ToNativeString) { + TestAtomicString([](JSContext* ctx) { + AtomicString&& value = AtomicString(ctx, "helloworld"); + auto native_string = value.ToNativeString(); + const uint16_t* p = native_string->string(); + EXPECT_EQ(native_string->length(), 10); + + uint16_t result[10] = {'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'}; + for (int i = 0; i < native_string->length(); i++) { + EXPECT_EQ(result[i], p[i]); + } + }); +} + +TEST(AtomicString, CopyAssignment) { + TestAtomicString([](JSContext* ctx) { + AtomicString str = AtomicString(ctx, "helloworld"); + struct P { + AtomicString str; + }; + P p{AtomicString::Empty(ctx)}; + p.str = str; + EXPECT_EQ(p.str == str, true); + }); +} + +TEST(AtomicString, MoveAssignment) { + TestAtomicString([](JSContext* ctx) { + auto&& str = AtomicString(ctx, "helloworld"); + auto&& str2 = AtomicString(std::move(str)); + EXPECT_STREQ(str2.ToStdString().c_str(), "helloworld"); + }); +} + +TEST(AtomicString, CopyToRightReference) { + TestAtomicString([](JSContext* ctx) { + AtomicString str = AtomicString::Empty(ctx); + if (1 + 1 == 2) { + str = AtomicString(ctx, "helloworld"); + } + EXPECT_STREQ(str.ToStdString().c_str(), "helloworld"); + }); +} diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc new file mode 100644 index 0000000000..9e68e3a854 --- /dev/null +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "binding_initializer.h" +#include "core/executing_context.h" + +#include "qjs_blob.h" +#include "qjs_bounding_client_rect.h" +#include "qjs_character_data.h" +#include "qjs_comment.h" +#include "qjs_console.h" +#include "qjs_css_style_declaration.h" +#include "qjs_document.h" +#include "qjs_element.h" +#include "qjs_element_attributes.h" +#include "qjs_error_event.h" +#include "qjs_event.h" +#include "qjs_event_target.h" +#include "qjs_html_body_element.h" +#include "qjs_html_div_element.h" +#include "qjs_html_element.h" +#include "qjs_html_head_element.h" +#include "qjs_html_html_element.h" +#include "qjs_html_template_element.h" +#include "qjs_html_unknown_element.h" +#include "qjs_location.h" +#include "qjs_message_event.h" +#include "qjs_module_manager.h" +#include "qjs_node.h" +#include "qjs_node_list.h" +#include "qjs_screen.h" +#include "qjs_text.h" +#include "qjs_window.h" +#include "qjs_window_or_worker_global_scope.h" + +namespace kraken { + +void InstallBindings(ExecutingContext* context) { + // Must follow the inheritance order when install. + // Exp: Node extends EventTarget, EventTarget must be install first. + QJSWindowOrWorkerGlobalScope::Install(context); + QJSLocation::Install(context); + QJSModuleManager::Install(context); + QJSConsole::Install(context); + QJSEventTarget::Install(context); + QJSWindow::Install(context); + QJSEvent::Install(context); + QJSErrorEvent::Install(context); + QJSMessageEvent::Install(context); + QJSNode::Install(context); + QJSNodeList::Install(context); + QJSDocument::Install(context); + QJSCharacterData::Install(context); + QJSText::Install(context); + QJSComment::Install(context); + QJSElement::Install(context); + QJSHTMLElement::Install(context); + QJSHTMLDivElement::Install(context); + QJSHTMLHeadElement::Install(context); + QJSHTMLBodyElement::Install(context); + QJSHTMLHtmlElement::Install(context); + QJSHTMLUnknownElement::Install(context); + QJSHTMLTemplateElement::Install(context); + QJSCSSStyleDeclaration::Install(context); + QJSBoundingClientRect::Install(context); + QJSScreen::Install(context); + QJSBlob::Install(context); + + // Legacy bindings, not standard. + QJSElementAttributes::Install(context); +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h new file mode 100644 index 0000000000..fca2265bf1 --- /dev/null +++ b/bridge/bindings/qjs/binding_initializer.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDING_INITIALIZER_H +#define KRAKENBRIDGE_BINDING_INITIALIZER_H + +#include + +namespace kraken { + +class ExecutingContext; + +void InstallBindings(ExecutingContext* context); + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDING_INITIALIZER_H diff --git a/bridge/bindings/qjs/bom/blob.cc b/bridge/bindings/qjs/bom/blob.cc deleted file mode 100644 index 65f6a28fb6..0000000000 --- a/bridge/bindings/qjs/bom/blob.cc +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "blob.h" -#include "dart_methods.h" - -namespace kraken::binding::qjs { - -std::once_flag kBlobInitOnceFlag; - -void bindBlob(ExecutionContext* context) { - auto* constructor = Blob::instance(context); - context->defineGlobalProperty("Blob", constructor->jsObject); -} - -Blob::Blob(ExecutionContext* context) : HostClass(context, "Blob") { - std::call_once(kBlobInitOnceFlag, []() { JS_NewClassID(&kBlobClassID); }); -} - -JSClassID Blob::kBlobClassID{0}; - -JSValue Blob::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - BlobBuilder builder; - auto constructor = static_cast(JS_GetOpaque(func_obj, ExecutionContext::kHostClassClassId)); - if (argc == 0) { - auto blob = new BlobInstance(constructor); - return blob->jsObject; - } - - JSValue arrayValue = argv[0]; - JSValue optionValue = JS_UNDEFINED; - - if (argc > 1) { - optionValue = argv[1]; - } - - if (!JS_IsArray(ctx, arrayValue)) { - return JS_ThrowTypeError(ctx, "Failed to construct 'Blob': The provided value cannot be converted to a sequence"); - } - - if (argc == 1 || JS_IsUndefined(optionValue)) { - builder.append(*constructor->m_context, arrayValue); - auto blob = new BlobInstance(constructor, builder.finalize()); - return blob->jsObject; - } - - if (!JS_IsObject(optionValue)) { - return JS_ThrowTypeError(ctx, - "Failed to construct 'Blob': parameter 2 ('options') " - "is not an object"); - } - - JSAtom mimeTypeKey = JS_NewAtom(ctx, "type"); - - JSValue mimeTypeValue = JS_GetProperty(ctx, optionValue, mimeTypeKey); - builder.append(*constructor->m_context, mimeTypeValue); - const char* cMineType = JS_ToCString(ctx, mimeTypeValue); - std::string mimeType = std::string(cMineType); - - auto* blob = new BlobInstance(constructor, builder.finalize(), mimeType); - - JS_FreeValue(ctx, mimeTypeValue); - JS_FreeCString(ctx, mimeType.c_str()); - JS_FreeAtom(ctx, mimeTypeKey); - - return blob->jsObject; -} - -IMPL_PROPERTY_GETTER(Blob, type)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* blobInstance = static_cast(JS_GetOpaque(this_val, Blob::kBlobClassID)); - return JS_NewString(blobInstance->m_ctx, blobInstance->mimeType.empty() ? "" : blobInstance->mimeType.c_str()); -} - -IMPL_PROPERTY_GETTER(Blob, size)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* blobInstance = static_cast(JS_GetOpaque(this_val, Blob::kBlobClassID)); - return JS_NewFloat64(blobInstance->m_ctx, blobInstance->_size); -} - -JSValue Blob::arrayBuffer(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - JSValue resolving_funcs[2]; - JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); - - auto blob = static_cast(JS_GetOpaque(this_val, Blob::kBlobClassID)); - - JS_DupValue(ctx, blob->jsObject); - - auto* promiseContext = new PromiseContext{blob, blob->m_context, resolving_funcs[0], resolving_funcs[1], promise}; - auto callback = [](void* callbackContext, int32_t contextId, const char* errmsg) { - if (!isContextValid(contextId)) - return; - auto* promiseContext = static_cast(callbackContext); - auto* blob = static_cast(promiseContext->data); - JSContext* ctx = blob->m_ctx; - - JSValue arrayBuffer = JS_NewArrayBuffer( - ctx, blob->bytes(), blob->size(), [](JSRuntime* rt, void* opaque, void* ptr) {}, nullptr, false); - JSValue arguments[] = {arrayBuffer}; - JSValue returnValue = JS_Call(ctx, promiseContext->resolveFunc, blob->context()->global(), 1, arguments); - JS_FreeValue(ctx, returnValue); - - blob->context()->drainPendingPromiseJobs(); - - if (JS_IsException(returnValue)) { - blob->context()->handleException(&returnValue); - return; - } - - JS_FreeValue(ctx, promiseContext->resolveFunc); - JS_FreeValue(ctx, promiseContext->rejectFunc); - JS_FreeValue(ctx, arrayBuffer); - JS_FreeValue(ctx, blob->jsObject); - list_del(&promiseContext->link); - delete promiseContext; - }; - list_add_tail(&promiseContext->link, &blob->m_context->promise_job_list); - - // TODO: remove setTimeout - getDartMethod()->setTimeout(promiseContext, blob->context()->getContextId(), callback, 0); - - return promise; -} - -JSValue Blob::slice(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - JSValue startValue = argv[0]; - JSValue endValue = argv[1]; - JSValue contentTypeValue = argv[2]; - - auto* blob = static_cast(JS_GetOpaque(this_val, Blob::kBlobClassID)); - int32_t start = 0; - int32_t end = blob->_data.size(); - std::string mimeType = blob->mimeType; - - if (argc > 0 && !JS_IsUndefined(startValue)) { - JS_ToInt32(ctx, &start, startValue); - } - - if (argc > 1 && !JS_IsUndefined(endValue)) { - JS_ToInt32(ctx, &end, endValue); - } - - if (argc > 2 && !JS_IsUndefined(contentTypeValue)) { - const char* cmimeType = JS_ToCString(ctx, contentTypeValue); - mimeType = std::string(cmimeType); - JS_FreeCString(ctx, mimeType.c_str()); - } - - if (start == 0 && end == blob->_data.size()) { - auto newBlob = new BlobInstance(reinterpret_cast(blob->m_hostClass), std::move(blob->_data), mimeType); - return newBlob->jsObject; - } - std::vector newData; - newData.reserve(blob->_data.size() - (end - start)); - newData.insert(newData.begin(), blob->_data.begin() + start, blob->_data.end() - (blob->_data.size() - end)); - - auto newBlob = new BlobInstance(reinterpret_cast(blob->m_hostClass), std::move(newData), mimeType); - return newBlob->jsObject; -} - -JSValue Blob::text(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - JSValue resolving_funcs[2]; - JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); - - auto blob = static_cast(JS_GetOpaque(this_val, Blob::kBlobClassID)); - JS_DupValue(ctx, blob->jsObject); - - auto* promiseContext = new PromiseContext{blob, blob->m_context, resolving_funcs[0], resolving_funcs[1], promise}; - auto callback = [](void* callbackContext, int32_t contextId, const char* errmsg) { - if (!isContextValid(contextId)) - return; - - auto* promiseContext = static_cast(callbackContext); - auto* blob = static_cast(promiseContext->data); - JSContext* ctx = blob->m_ctx; - - JSValue text = JS_NewStringLen(ctx, reinterpret_cast(blob->bytes()), blob->size()); - JSValue arguments[] = {text}; - JSValue returnValue = JS_Call(ctx, promiseContext->resolveFunc, blob->context()->global(), 1, arguments); - JS_FreeValue(ctx, returnValue); - - blob->context()->drainPendingPromiseJobs(); - - if (JS_IsException(returnValue)) { - blob->context()->handleException(&returnValue); - return; - } - - JS_FreeValue(ctx, promiseContext->resolveFunc); - JS_FreeValue(ctx, promiseContext->rejectFunc); - JS_FreeValue(ctx, text); - JS_FreeValue(ctx, blob->jsObject); - list_del(&promiseContext->link); - delete promiseContext; - }; - list_add_tail(&promiseContext->link, &blob->m_context->promise_job_list); - - getDartMethod()->setTimeout(promiseContext, blob->context()->getContextId(), callback, 0); - - return promise; -} - -void BlobInstance::finalize(JSRuntime* rt, JSValue val) { - auto* eventTarget = static_cast(JS_GetOpaque(val, Blob::kBlobClassID)); - delete eventTarget; -} - -void BlobBuilder::append(ExecutionContext& context, BlobInstance* blob) { - std::vector blobData = blob->_data; - _data.reserve(_data.size() + blobData.size()); - _data.insert(_data.end(), blobData.begin(), blobData.end()); -} - -void BlobBuilder::append(ExecutionContext& context, JSValue& value) { - if (JS_IsString(value)) { - const char* buffer = JS_ToCString(context.ctx(), value); - std::string str = std::string(buffer); - std::vector strArr(str.begin(), str.end()); - _data.reserve(_data.size() + strArr.size()); - _data.insert(_data.end(), strArr.begin(), strArr.end()); - JS_FreeCString(context.ctx(), buffer); - } else if (JS_IsArray(context.ctx(), value)) { - JSAtom lengthKey = JS_NewAtom(context.ctx(), "length"); - JSValue lengthValue = JS_GetProperty(context.ctx(), value, lengthKey); - uint32_t length; - JS_ToUint32(context.ctx(), &length, lengthValue); - - JS_FreeValue(context.ctx(), lengthValue); - JS_FreeAtom(context.ctx(), lengthKey); - - for (size_t i = 0; i < length; i++) { - JSValue v = JS_GetPropertyUint32(context.ctx(), value, i); - append(context, v); - JS_FreeValue(context.ctx(), v); - } - } else if (JS_IsObject(value)) { - if (JS_IsInstanceOf(context.ctx(), value, Blob::instance(&context)->jsObject)) { - auto blob = static_cast(JS_GetOpaque(value, Blob::kBlobClassID)); - if (blob == nullptr) - return; - if (std::string(blob->m_name) == "Blob") { - std::vector blobData = blob->_data; - _data.reserve(_data.size() + blobData.size()); - _data.insert(_data.end(), blobData.begin(), blobData.end()); - } - } else { - size_t length; - uint8_t* buffer = JS_GetArrayBuffer(context.ctx(), &length, value); - - if (buffer == nullptr) { - size_t byte_offset; - size_t byte_length; - size_t byte_per_element; - JSValue arrayBufferObject = JS_GetTypedArrayBuffer(context.ctx(), value, &byte_offset, &byte_length, &byte_per_element); - if (JS_IsException(arrayBufferObject)) { - context.handleException(&arrayBufferObject); - return; - } - buffer = JS_GetArrayBuffer(context.ctx(), &length, arrayBufferObject); - JS_FreeValue(context.ctx(), arrayBufferObject); - } - - for (size_t i = 0; i < length; i++) { - _data.emplace_back(buffer[i]); - } - } - } -} - -std::vector BlobBuilder::finalize() { - return std::move(_data); -} - -int32_t BlobInstance::size() { - return _data.size(); -} - -uint8_t* BlobInstance::bytes() { - return _data.data(); -} -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/blob.h b/bridge/bindings/qjs/bom/blob.h deleted file mode 100644 index 360364b6dd..0000000000 --- a/bridge/bindings/qjs/bom/blob.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_BLOB_H -#define KRAKENBRIDGE_BLOB_H - -#include "bindings/qjs/host_class.h" - -namespace kraken::binding::qjs { - -class BlobBuilder; -class BlobInstance; - -void bindBlob(ExecutionContext* context); - -class Blob : public HostClass { - public: - static JSClassID kBlobClassID; - OBJECT_INSTANCE(Blob); - - Blob() = delete; - explicit Blob(ExecutionContext* context); - - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - static JSValue arrayBuffer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue slice(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue text(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - private: - friend BlobInstance; - DEFINE_PROTOTYPE_READONLY_PROPERTY(type); - DEFINE_PROTOTYPE_READONLY_PROPERTY(size); - - DEFINE_PROTOTYPE_FUNCTION(arrayBuffer, 0); - DEFINE_PROTOTYPE_FUNCTION(slice, 3); - DEFINE_PROTOTYPE_FUNCTION(text, 0); -}; - -class BlobInstance : public Instance { - public: - BlobInstance() = delete; - explicit BlobInstance(Blob* blob) : Instance(blob, "Blob", nullptr, Blob::kBlobClassID, finalize){}; - explicit BlobInstance(Blob* blob, std::vector&& data) : _size(data.size()), _data(std::move(data)), Instance(blob, "Blob", nullptr, Blob::kBlobClassID, finalize){}; - explicit BlobInstance(Blob* blob, std::vector&& data, std::string& mime) - : mimeType(mime), _size(data.size()), _data(std::move(data)), Instance(blob, "Blob", nullptr, Blob::kBlobClassID, finalize){}; - - /// get an pointer of bytes data from JSBlob - uint8_t* bytes(); - /// get bytes data's length - int32_t size(); - - private: - size_t _size; - std::string mimeType{""}; - std::vector _data; - friend BlobBuilder; - friend Blob; - - static void finalize(JSRuntime* rt, JSValue val); -}; - -class BlobBuilder { - public: - void append(ExecutionContext& context, JSValue& value); - void append(ExecutionContext& context, BlobInstance* blob); - - std::vector finalize(); - - private: - friend Blob; - std::vector _data; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_BLOB_H diff --git a/bridge/bindings/qjs/bom/console.cc b/bridge/bindings/qjs/bom/console.cc deleted file mode 100644 index 8fdc02f4b6..0000000000 --- a/bridge/bindings/qjs/bom/console.cc +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "console.h" -#include "foundation/logging.h" - -namespace kraken::binding::qjs { - -JSValue print(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - std::stringstream stream; - JSValue log = argv[0]; - if (JS_IsString(log)) { - const char* buffer = JS_ToCString(ctx, log); - stream << buffer; - JS_FreeCString(ctx, buffer); - } else { - return JS_ThrowTypeError(ctx, "Failed to execute 'print': log must be string."); - } - - auto* context = static_cast(JS_GetContextOpaque(ctx)); - const char* logLevel = "info"; - JSValue level = argv[1]; - if (JS_IsString(level)) { - logLevel = JS_ToCString(ctx, level); - JS_FreeCString(ctx, logLevel); - } - - foundation::printLog(context->getContextId(), stream, logLevel, nullptr); - return JS_UNDEFINED; -} - -void bindConsole(ExecutionContext* context) { - QJS_GLOBAL_BINDING_FUNCTION(context, print, "__kraken_print__", 2); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/console.h b/bridge/bindings/qjs/bom/console.h deleted file mode 100644 index 433189ccaa..0000000000 --- a/bridge/bindings/qjs/bom/console.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_CONSOLE_H -#define KRAKENBRIDGE_CONSOLE_H - -#include "bindings/qjs/executing_context.h" - -namespace kraken::binding::qjs { - -void bindConsole(ExecutionContext* context); - -} - -#endif // KRAKENBRIDGE_CONSOLE_H diff --git a/bridge/bindings/qjs/bom/dom_timer_coordinator.cc b/bridge/bindings/qjs/bom/dom_timer_coordinator.cc deleted file mode 100644 index 6623d046e0..0000000000 --- a/bridge/bindings/qjs/bom/dom_timer_coordinator.cc +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "dom_timer_coordinator.h" -#include "dart_methods.h" -#include "timer.h" - -#if UNIT_TEST -#include "kraken_test_env.h" -#endif - -namespace kraken::binding::qjs { - -static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); - - if (errmsg != nullptr) { - JSValue exception = JS_ThrowTypeError(timer->ctx(), "%s", errmsg); - context->handleException(&exception); - return; - } - - // Trigger timer callbacks. - timer->fire(); - - // Executing pending async jobs. - context->drainPendingPromiseJobs(); -} - -static void handleTransientCallback(void* ptr, int32_t contextId, const char* errmsg) { - auto* timer = static_cast(ptr); - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); - - if (!checkPage(contextId, context)) - return; - if (!context->isValid()) - return; - - handleTimerCallback(timer, errmsg); - - context->timers()->removeTimeoutById(timer->timerId()); -} - -void DOMTimerCoordinator::installNewTimer(ExecutionContext* context, int32_t timerId, DOMTimer* timer) { - m_activeTimers[timerId] = timer; -} - -void* DOMTimerCoordinator::removeTimeoutById(int32_t timerId) { - if (m_activeTimers.count(timerId) == 0) - return nullptr; - DOMTimer* timer = m_activeTimers[timerId]; - - // Push this timer to abandoned list to mark this timer is deprecated. - m_abandonedTimers.emplace_back(timer); - - m_activeTimers.erase(timerId); - return nullptr; -} - -DOMTimer* DOMTimerCoordinator::getTimerById(int32_t timerId) { - if (m_activeTimers.count(timerId) == 0) - return nullptr; - return m_activeTimers[timerId]; -} - -void DOMTimerCoordinator::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - for (auto& timer : m_activeTimers) { - JS_MarkValue(rt, timer.second->toQuickJS(), mark_func); - } - - // Recycle all abandoned timers. - if (!m_abandonedTimers.empty()) { - for (auto& timer : m_abandonedTimers) { - JS_MarkValue(rt, timer->toQuickJS(), mark_func); - } - // All abandoned timers should be freed at the sweep stage. - m_abandonedTimers.clear(); - } -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/location.cc b/bridge/bindings/qjs/bom/location.cc deleted file mode 100644 index 7d253a10eb..0000000000 --- a/bridge/bindings/qjs/bom/location.cc +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "location.h" -#include -#include "dart_methods.h" - -namespace kraken::binding::qjs { - -JSValue Location::reload(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* location = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - if (getDartMethod()->reloadApp == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute 'reload': dart method (reloadApp) is not registered."); - } - - getDartMethod()->flushUICommand(); - getDartMethod()->reloadApp(location->m_context->getContextId()); - - return JS_NULL; -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/location.h b/bridge/bindings/qjs/bom/location.h deleted file mode 100644 index e21a26b73d..0000000000 --- a/bridge/bindings/qjs/bom/location.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_LOCATION_H -#define KRAKENBRIDGE_LOCATION_H - -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/host_object.h" - -namespace kraken::binding::qjs { - -class Location : public HostObject { - public: - Location() = delete; - explicit Location(ExecutionContext* context) : HostObject(context, "Location") {} - - static JSValue reload(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - private: - DEFINE_FUNCTION(reload, 0); -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_LOCATION_H diff --git a/bridge/bindings/qjs/bom/screen.cc b/bridge/bindings/qjs/bom/screen.cc deleted file mode 100644 index b8ac2ce642..0000000000 --- a/bridge/bindings/qjs/bom/screen.cc +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "screen.h" - -namespace kraken::binding::qjs { - -void bindScreen(ExecutionContext* context) { - auto* screen = new Screen(context); - context->defineGlobalProperty("screen", screen->jsObject); -} - -IMPL_PROPERTY_GETTER(Screen, width)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (getDartMethod()->getScreen == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to read screen: dart method (getScreen) is not registered."); - } - - auto context = static_cast(JS_GetContextOpaque(ctx)); - NativeScreen* screen = getDartMethod()->getScreen(context->getContextId()); - return JS_NewFloat64(ctx, screen->width); -} - -IMPL_PROPERTY_GETTER(Screen, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (getDartMethod()->getScreen == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to read screen: dart method (getScreen) is not registered."); - } - - auto context = static_cast(JS_GetContextOpaque(ctx)); - NativeScreen* screen = getDartMethod()->getScreen(context->getContextId()); - return JS_NewFloat64(ctx, screen->height); -} - -// https://drafts.csswg.org/cssom-view/#dom-screen-availwidth -IMPL_PROPERTY_GETTER(Screen, availWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (getDartMethod()->getScreen == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to read screen: dart method (getScreen) is not registered."); - } - - auto context = static_cast(JS_GetContextOpaque(ctx)); - NativeScreen* screen = getDartMethod()->getScreen(context->getContextId()); - return JS_NewFloat64(ctx, screen->width); -} - -// https://drafts.csswg.org/cssom-view/#dom-screen-availheight -IMPL_PROPERTY_GETTER(Screen, availHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (getDartMethod()->getScreen == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to read screen: dart method (getScreen) is not registered."); - } - - auto context = static_cast(JS_GetContextOpaque(ctx)); - NativeScreen* screen = getDartMethod()->getScreen(context->getContextId()); - return JS_NewFloat64(ctx, screen->height); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/screen.h b/bridge/bindings/qjs/bom/screen.h deleted file mode 100644 index b454f94727..0000000000 --- a/bridge/bindings/qjs/bom/screen.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_SCREEN_H -#define KRAKENBRIDGE_SCREEN_H - -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/host_object.h" -#include "dart_methods.h" - -namespace kraken::binding::qjs { - -class Screen : public HostObject { - public: - explicit Screen(ExecutionContext* context) : HostObject(context, "Screen"){}; - - private: - DEFINE_READONLY_PROPERTY(width); - DEFINE_READONLY_PROPERTY(height); - DEFINE_READONLY_PROPERTY(availWidth); - DEFINE_READONLY_PROPERTY(availHeight); -}; - -void bindScreen(ExecutionContext* context); - -} // namespace kraken::binding::qjs - -class screen {}; - -#endif // KRAKENBRIDGE_SCREEN_H diff --git a/bridge/bindings/qjs/bom/timer.cc b/bridge/bindings/qjs/bom/timer.cc deleted file mode 100644 index 88a3e73d31..0000000000 --- a/bridge/bindings/qjs/bom/timer.cc +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "timer.h" -#include "bindings/qjs/garbage_collected.h" -#include "bindings/qjs/qjs_patch.h" -#include "dart_methods.h" - -#if UNIT_TEST -#include "kraken_test_env.h" -#endif - -namespace kraken::binding::qjs { - -DOMTimer::DOMTimer(JSValue callback) : m_callback(callback) {} - -JSClassID DOMTimer::classId{0}; - -void DOMTimer::fire() { - // 'callback' might be destroyed when calling itself (if it frees the handler), so must take extra care. - auto* context = static_cast(JS_GetContextOpaque(m_ctx)); - if (!JS_IsFunction(m_ctx, m_callback)) - return; - - JS_DupValue(m_ctx, m_callback); - JSValue returnValue = JS_Call(m_ctx, m_callback, JS_UNDEFINED, 0, nullptr); - JS_FreeValue(m_ctx, m_callback); - - if (JS_IsException(returnValue)) { - context->handleException(&returnValue); - } - - JS_FreeValue(m_ctx, returnValue); -} - -void DOMTimer::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - JS_MarkValue(rt, m_callback, mark_func); -} - -void DOMTimer::dispose() const { - JS_FreeValueRT(m_runtime, m_callback); -} - -int32_t DOMTimer::timerId() { - return m_timerId; -} - -void DOMTimer::setTimerId(int32_t timerId) { - m_timerId = timerId; -} - -static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); - - if (errmsg != nullptr) { - JSValue exception = JS_ThrowTypeError(timer->ctx(), "%s", errmsg); - context->handleException(&exception); - return; - } - - if (context->timers()->getTimerById(timer->timerId()) == nullptr) - return; - - // Trigger timer callbacks. - timer->fire(); - - // Executing pending async jobs. - context->drainPendingPromiseJobs(); -} - -static void handleTransientCallback(void* ptr, int32_t contextId, const char* errmsg) { - auto* timer = static_cast(ptr); - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); - - if (!checkPage(contextId, context)) - return; - if (!context->isValid()) - return; - - handleTimerCallback(timer, errmsg); - - context->timers()->removeTimeoutById(timer->timerId()); -} - -static void handlePersistentCallback(void* ptr, int32_t contextId, const char* errmsg) { - auto* timer = static_cast(ptr); - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); - - if (!checkPage(contextId, context)) - return; - if (!context->isValid()) - return; - - handleTimerCallback(timer, errmsg); -} - -static JSValue setTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': 1 argument required, but only 0 present."); - } - - auto context = static_cast(JS_GetContextOpaque(ctx)); - JSValue callbackValue = argv[0]; - JSValue timeoutValue = argv[1]; - - if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': parameter 1 (callback) must be a function."); - } - - if (!JS_IsFunction(ctx, callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': parameter 1 (callback) must be a function."); - } - - int32_t timeout; - - if (argc < 2 || JS_IsUndefined(timeoutValue)) { - timeout = 0; - } else if (JS_IsNumber(timeoutValue)) { - JS_ToInt32(ctx, &timeout, timeoutValue); - } else { - return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': parameter 2 (timeout) only can be a number or undefined."); - } - -#if FLUTTER_BACKEND - if (getDartMethod()->setTimeout == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': dart method (setTimeout) is not registered."); - } -#endif - - // Create a timer object to keep track timer callback. - auto* timer = makeGarbageCollected(JS_DupValue(ctx, callbackValue))->initialize(context->ctx(), &DOMTimer::classId); - - auto timerId = getDartMethod()->setTimeout(timer, context->getContextId(), handleTransientCallback, timeout); - - // Register timerId. - timer->setTimerId(timerId); - - context->timers()->installNewTimer(context, timerId, timer); - - // `-1` represents ffi error occurred. - if (timerId == -1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': dart method (setTimeout) execute failed"); - } - - return JS_NewUint32(ctx, timerId); -} - -static JSValue setInterval(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setInterval': 1 argument required, but only 0 present."); - } - - auto context = static_cast(JS_GetContextOpaque(ctx)); - JSValue callbackValue = argv[0]; - JSValue timeoutValue = argv[1]; - - if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setInterval': parameter 1 (callback) must be a function."); - } - - if (!JS_IsFunction(ctx, callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setInterval': parameter 1 (callback) must be a function."); - } - - int32_t timeout; - - if (argc < 2 || JS_IsUndefined(timeoutValue)) { - timeout = 0; - } else if (JS_IsNumber(timeoutValue)) { - JS_ToInt32(ctx, &timeout, timeoutValue); - } else { - return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': parameter 2 (timeout) only can be a number or undefined."); - } - - if (getDartMethod()->setInterval == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setInterval': dart method (setInterval) is not registered."); - } - - // Create a timer object to keep track timer callback. - auto* timer = makeGarbageCollected(JS_DupValue(ctx, callbackValue))->initialize(context->ctx(), &DOMTimer::classId); - - uint32_t timerId = getDartMethod()->setInterval(timer, context->getContextId(), handlePersistentCallback, timeout); - - // Register timerId. - timer->setTimerId(timerId); - context->timers()->installNewTimer(context, timerId, timer); - - if (timerId == -1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setInterval': dart method (setInterval) got unexpected error."); - } - - return JS_NewUint32(ctx, timerId); -} - -static JSValue clearTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc <= 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'clearTimeout': 1 argument required, but only 0 present."); - } - - auto context = static_cast(JS_GetContextOpaque(ctx)); - - JSValue timeIdValue = argv[0]; - if (!JS_IsNumber(timeIdValue)) { - return JS_NULL; - } - - int32_t id; - JS_ToInt32(ctx, &id, timeIdValue); - - if (getDartMethod()->clearTimeout == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute 'clearTimeout': dart method (clearTimeout) is not registered."); - } - - getDartMethod()->clearTimeout(context->getContextId(), id); - - context->timers()->removeTimeoutById(id); - return JS_NULL; -} - -void bindTimer(ExecutionContext* context) { - QJS_GLOBAL_BINDING_FUNCTION(context, setTimeout, "setTimeout", 2); - QJS_GLOBAL_BINDING_FUNCTION(context, setInterval, "setInterval", 2); - QJS_GLOBAL_BINDING_FUNCTION(context, clearTimeout, "clearTimeout", 1); - QJS_GLOBAL_BINDING_FUNCTION(context, clearTimeout, "clearInterval", 1); -} -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/timer.h b/bridge/bindings/qjs/bom/timer.h deleted file mode 100644 index 23cb6a9b98..0000000000 --- a/bridge/bindings/qjs/bom/timer.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_TIMER_H -#define KRAKENBRIDGE_TIMER_H - -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/garbage_collected.h" -#include "dom_timer_coordinator.h" - -namespace kraken::binding::qjs { - -class DOMTimer : public GarbageCollected { - public: - static JSClassID classId; - DOMTimer(JSValue callback); - - // Trigger timer callback. - void fire(); - - int32_t timerId(); - void setTimerId(int32_t timerId); - - [[nodiscard]] FORCE_INLINE const char* getHumanReadableName() const override { return "DOMTimer"; } - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; - void dispose() const override; - - private: - int32_t m_timerId{-1}; - int32_t m_isInterval{false}; - JSValue m_callback; -}; - -void bindTimer(ExecutionContext* context); - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_TIMER_H diff --git a/bridge/bindings/qjs/bom/window.cc b/bridge/bindings/qjs/bom/window.cc deleted file mode 100644 index dabc8addd7..0000000000 --- a/bridge/bindings/qjs/bom/window.cc +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "window.h" -#include "bindings/qjs/dom/document.h" -#include "bindings/qjs/dom/events/.gen/message_event.h" -#include "bindings/qjs/garbage_collected.h" -#include "bindings/qjs/qjs_patch.h" -#include "dart_methods.h" - -namespace kraken::binding::qjs { - -std::once_flag kWindowInitOnceFlag; - -void bindWindow(ExecutionContext* context) { - // Set globalThis and Window's prototype to EventTarget's prototype to support EventTarget methods in global. - auto* windowConstructor = new Window(context); - JS_SetPrototype(context->ctx(), context->global(), windowConstructor->prototype()); - context->defineGlobalProperty("Window", windowConstructor->jsObject); - - auto* window = new WindowInstance(windowConstructor); - JS_SetOpaque(context->global(), window); - context->defineGlobalProperty("__window__", window->jsObject); -} - -JSValue ensureWindowIsGlobal(EventTargetInstance* target) { - if (target == target->context()->window()) { - return target->context()->global(); - } - return target->jsObject; -} - -JSClassID Window::kWindowClassId{0}; - -Window::Window(ExecutionContext* context) : EventTarget(context, "Window") { - std::call_once(kWindowInitOnceFlag, []() { JS_NewClassID(&kWindowClassId); }); - JS_SetPrototype(m_ctx, m_prototypeObject, EventTarget::instance(m_context)->prototype()); -} - -JSClassID Window::classId() { - return 1; -} - -JSValue Window::open(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto window = static_cast(JS_GetOpaque(this_val, Window::classId())); - NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0])}; - return window->invokeBindingMethod("open", 1, arguments); -} -JSValue Window::scrollTo(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -#if FLUTTER_BACKEND - getDartMethod()->flushUICommand(); - auto window = static_cast(JS_GetOpaque(this_val, Window::classId())); - double arg0 = 0; - double arg1 = 0; - JS_ToFloat64(ctx, &arg0, argv[0]); - JS_ToFloat64(ctx, &arg1, argv[1]); - NativeValue arguments[] = {Native_NewFloat64(arg0), Native_NewFloat64(arg1)}; - return window->invokeBindingMethod("scroll", 2, arguments); -#else - return JS_UNDEFINED; -#endif -} -JSValue Window::scrollBy(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto window = static_cast(JS_GetOpaque(this_val, Window::classId())); - double arg0 = 0; - double arg1 = 0; - JS_ToFloat64(ctx, &arg0, argv[0]); - JS_ToFloat64(ctx, &arg1, argv[1]); - NativeValue arguments[] = {Native_NewFloat64(arg0), Native_NewFloat64(arg1)}; - return window->invokeBindingMethod("scrollBy", 2, arguments); -} - -JSValue Window::postMessage(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - JSValue messageValue = argv[0]; - JSValue globalObjectValue = JS_GetGlobalObject(ctx); - auto* window = static_cast(JS_GetOpaque(globalObjectValue, Window::classId())); - - JSValue messageEventInitValue = JS_NewObject(ctx); - - JS_SetPropertyStr(ctx, messageEventInitValue, "data", JS_DupValue(ctx, messageValue)); - // TODO: convert originValue to current src. - JS_SetPropertyStr(ctx, messageEventInitValue, "origin", JS_NewString(ctx, "")); - - JSValue messageType = JS_NewString(ctx, "message"); - JSValue arguments[] = {messageType, messageEventInitValue}; - - JSValue messageEventValue = JS_CallConstructor(ctx, MessageEvent::instance(window->m_context)->jsObject, 2, arguments); - auto* event = static_cast(JS_GetOpaque(messageEventValue, Event::kEventClassID)); - window->dispatchEvent(event); - - JS_FreeValue(ctx, messageType); - JS_FreeValue(ctx, messageEventValue); - JS_FreeValue(ctx, messageEventInitValue); - JS_FreeValue(ctx, globalObjectValue); - return JS_NULL; -} - -JSValue Window::requestAnimationFrame(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc <= 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': 1 argument required, but only 0 present."); - } - - auto* context = static_cast(JS_GetContextOpaque(ctx)); - auto window = static_cast(JS_GetOpaque(context->global(), Window::classId())); - - JSValue callbackValue = argv[0]; - - if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); - } - - if (!JS_IsFunction(ctx, callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); - } - - // Flutter backend implements check -#if FLUTTER_BACKEND - if (getDartMethod()->flushUICommand == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); - } - // Flush all pending ui messages. - getDartMethod()->flushUICommand(); - - if (getDartMethod()->requestAnimationFrame == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) is not registered."); - } -#endif - - auto* frameCallback = makeGarbageCollected(JS_DupValue(ctx, callbackValue))->initialize(ctx, &FrameCallback::classId); - - int32_t requestId = window->document()->requestAnimationFrame(frameCallback); - - // `-1` represents some error occurred. - if (requestId == -1) { - return JS_ThrowTypeError(ctx, - "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) executed " - "with unexpected error."); - } - - return JS_NewUint32(ctx, requestId); -} - -JSValue Window::cancelAnimationFrame(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc <= 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'cancelAnimationFrame': 1 argument required, but only 0 present."); - } - - auto context = static_cast(JS_GetContextOpaque(ctx)); - auto window = static_cast(JS_GetOpaque(context->global(), Window::classId())); - - JSValue requestIdValue = argv[0]; - if (!JS_IsNumber(requestIdValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'cancelAnimationFrame': parameter 1 (timer) is not a timer kind."); - } - - int32_t id; - JS_ToInt32(ctx, &id, requestIdValue); - - if (getDartMethod()->cancelAnimationFrame == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute 'cancelAnimationFrame': dart method (cancelAnimationFrame) is not registered."); - } - - window->document()->cancelAnimationFrame(id); - - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(Window, devicePixelRatio)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); - return window->getBindingProperty("devicePixelRatio"); -} - -IMPL_PROPERTY_GETTER(Window, colorScheme)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); - return window->getBindingProperty("colorScheme"); -} - -IMPL_PROPERTY_GETTER(Window, innerWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); - return window->getBindingProperty("innerWidth"); -} - -IMPL_PROPERTY_GETTER(Window, innerHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); - return window->getBindingProperty("innerHeight"); -} - -IMPL_PROPERTY_GETTER(Window, __location__)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); - if (window == nullptr) - return JS_UNDEFINED; - return JS_DupValue(ctx, window->m_location.value()); -} - -IMPL_PROPERTY_GETTER(Window, location)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); - return JS_GetPropertyStr(ctx, window->m_context->global(), "location"); -} - -IMPL_PROPERTY_GETTER(Window, window)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_GetGlobalObject(ctx); -} - -IMPL_PROPERTY_GETTER(Window, parent)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_GetGlobalObject(ctx); -} - -IMPL_PROPERTY_GETTER(Window, scrollX)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); - return window->getBindingProperty("scrollX"); -} - -IMPL_PROPERTY_GETTER(Window, scrollY)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); - return window->getBindingProperty("scrollY"); -} - -IMPL_PROPERTY_GETTER(Window, onerror)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); - return JS_DupValue(ctx, window->onerror); -} -IMPL_PROPERTY_SETTER(Window, onerror)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); - JSValue eventString = JS_NewString(ctx, "onerror"); - JSString* p = JS_VALUE_GET_STRING(eventString); - JSValue onerrorHandler = argv[0]; - window->setAttributesEventHandler(p, onerrorHandler); - - if (!JS_IsNull(window->onerror)) { - JS_FreeValue(ctx, window->onerror); - } - - window->onerror = JS_DupValue(ctx, onerrorHandler); - JS_FreeValue(ctx, eventString); - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(Window, self)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_GetGlobalObject(ctx); -} - -WindowInstance::WindowInstance(Window* window) : EventTargetInstance(window, Window::kWindowClassId, "window", WINDOW_TARGET_ID) { - if (getDartMethod()->initWindow != nullptr) { - getDartMethod()->initWindow(context()->getContextId(), nativeEventTarget); - } - m_context->m_window = this; -} - -void WindowInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - EventTargetInstance::trace(rt, val, mark_func); - - JS_MarkValue(rt, onerror, mark_func); -} - -DocumentInstance* WindowInstance::document() { - return m_context->m_document; -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/window.h b/bridge/bindings/qjs/bom/window.h deleted file mode 100644 index 9d796e002f..0000000000 --- a/bridge/bindings/qjs/bom/window.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_WINDOW_H -#define KRAKENBRIDGE_WINDOW_H - -#include "bindings/qjs/bom/location.h" -#include "bindings/qjs/dom/event_target.h" -#include "bindings/qjs/executing_context.h" - -namespace kraken::binding::qjs { - -void bindWindow(ExecutionContext* context); - -class WindowInstance; - -class Window : public EventTarget { - public: - static JSClassID kWindowClassId; - - static JSClassID classId(); - - static JSValue open(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue scrollTo(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue scrollBy(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue postMessage(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue requestAnimationFrame(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue cancelAnimationFrame(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - Window() = delete; - explicit Window(ExecutionContext* context); - - OBJECT_INSTANCE(Window); - - private: - DEFINE_PROTOTYPE_READONLY_PROPERTY(devicePixelRatio); - DEFINE_PROTOTYPE_READONLY_PROPERTY(colorScheme); - DEFINE_PROTOTYPE_READONLY_PROPERTY(__location__); - DEFINE_PROTOTYPE_READONLY_PROPERTY(location); - DEFINE_PROTOTYPE_READONLY_PROPERTY(window); - DEFINE_PROTOTYPE_READONLY_PROPERTY(parent); - DEFINE_PROTOTYPE_READONLY_PROPERTY(scrollX); - DEFINE_PROTOTYPE_READONLY_PROPERTY(scrollY); - DEFINE_PROTOTYPE_READONLY_PROPERTY(innerWidth); - DEFINE_PROTOTYPE_READONLY_PROPERTY(innerHeight); - DEFINE_PROTOTYPE_READONLY_PROPERTY(self); - - DEFINE_PROTOTYPE_PROPERTY(onerror); - - DEFINE_PROTOTYPE_FUNCTION(open, 1); - // ScrollTo is same as scroll which reuse scroll functions. Macro expand is not support here. - ObjectFunction m_scroll{m_context, m_prototypeObject, "scroll", scrollTo, 2}; - DEFINE_PROTOTYPE_FUNCTION(scrollTo, 2); - DEFINE_PROTOTYPE_FUNCTION(scrollBy, 2); - DEFINE_PROTOTYPE_FUNCTION(postMessage, 3); - DEFINE_PROTOTYPE_FUNCTION(requestAnimationFrame, 1); - DEFINE_PROTOTYPE_FUNCTION(cancelAnimationFrame, 1); - - friend WindowInstance; -}; - -// Hack: m_context->window() are not real global object, which are strict equal to globalThis. -// But we configure m_context->window() to simulate globalThis and can access all global properties and methods. -JSValue ensureWindowIsGlobal(EventTargetInstance* target); - -class WindowInstance : public EventTargetInstance { - public: - WindowInstance() = delete; - explicit WindowInstance(Window* window); - ~WindowInstance() {} - - private: - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; - DocumentInstance* document(); - - ObjectProperty m_location{m_context, jsObject, "m_location", (new Location(m_context))->jsObject}; - JSValue onerror{JS_NULL}; - friend Window; - friend ExecutionContext; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_WINDOW_H diff --git a/bridge/bindings/qjs/converter.h b/bridge/bindings/qjs/converter.h new file mode 100644 index 0000000000..1d76de01c6 --- /dev/null +++ b/bridge/bindings/qjs/converter.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CONVERTER_H +#define KRAKENBRIDGE_CONVERTER_H + +#include + +namespace kraken { + +// The template parameter |T| determines what kind of type conversion to perform. +// It is not supposed to be used directly: there needs to be a specialization for each type which represents +// a JavaScript type that will be converted to a C++ representation. +// Its main goal is to provide a standard interface for converting JS types +// into C++ ones. +template +struct Converter { + using ImplType = T; +}; + +template +struct ConverterBase { + using ImplType = typename T::ImplType; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CONVERTER_H diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h new file mode 100644 index 0000000000..2e5acc60bb --- /dev/null +++ b/bridge/bindings/qjs/converter_impl.h @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ + +#include +#include "atomic_string.h" +#include "converter.h" +#include "core/dom/document.h" +#include "core/dom/events/event.h" +#include "core/dom/events/event_target.h" +#include "core/dom/node_list.h" +#include "core/fileapi/blob_part.h" +#include "core/fileapi/blob_property_bag.h" +#include "core/frame/window.h" +#include "core/html/html_body_element.h" +#include "core/html/html_div_element.h" +#include "core/html/html_element.h" +#include "core/html/html_head_element.h" +#include "core/html/html_html_element.h" +#include "exception_message.h" +#include "idl_type.h" +#include "js_event_listener.h" +#include "native_string_utils.h" + +namespace kraken { + +template +struct is_shared_ptr : std::false_type {}; +template +struct is_shared_ptr> : std::true_type {}; + +// Optional value for pointer value. +template +struct Converter, std::enable_if_t::ImplType>::value>> + : public ConverterBase> { + using ImplType = typename Converter::ImplType; + + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { + if (JS_IsUndefined(value)) { + return nullptr; + } + return Converter::FromValue(ctx, value, exception); + } + + static ImplType ArgumentsValue(ExecutingContext* context, + JSValue value, + uint32_t argv_index, + ExceptionState& exception_state) { + if (JS_IsUndefined(value)) { + return nullptr; + } + return Converter::ArgumentsValue(context, value, argv_index, exception_state); + } + + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + if (value == nullptr) { + return JS_UNDEFINED; + } + + return Converter::ToValue(ctx, value); + } +}; + +// Nullable value for pointer value +template +struct Converter, std::enable_if_t::ImplType>::value>> + : public ConverterBase> { + using ImplType = typename Converter::ImplType; + + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsNull(value)) { + return nullptr; + } + return Converter::FromValue(ctx, value, exception_state); + } + + static ImplType ArgumentsValue(ExecutingContext* context, + JSValue value, + uint32_t argv_index, + ExceptionState& exception_state) { + if (JS_IsNull(value)) { + return nullptr; + } + return Converter::ArgumentsValue(context, value, argv_index, exception_state); + } + + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + if (value == nullptr) { + return JS_NULL; + } + + return Converter::ToValue(ctx, value); + } +}; + +template +struct Converter, std::enable_if_t::ImplType>::value>> + : public ConverterBase> { + using ImplType = typename Converter::ImplType; + + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { + if (JS_IsUndefined(value)) { + return nullptr; + } + return Converter::FromValue(ctx, value, exception); + } + + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + if (value == nullptr) { + return JS_UNDEFINED; + } + + return Converter::ToValue(ctx, value); + } +}; + +// Optional value for arithmetic value +template +struct Converter, std::enable_if_t::ImplType>::value>> + : public ConverterBase> { + using ImplType = typename Converter::ImplType; + + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { + if (JS_IsUndefined(value)) { + return 0; + } + return Converter::FromValue(ctx, value, exception); + } + + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, value); + } +}; + +// Any +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return ScriptValue(ctx, value); + } + + static JSValue ToValue(JSContext* ctx, ScriptValue value) { return JS_DupValue(ctx, value.QJSValue()); } +}; + +template <> +struct Converter> : public ConverterBase> { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return ScriptValue(ctx, value); + } + + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, std::move(value)); + } +}; + +template <> +struct Converter> : public ConverterBase> { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsNull(value)) { + return ScriptValue::Empty(ctx); + } + + assert(!JS_IsException(value)); + return ScriptValue(ctx, value); + } +}; + +// Boolean +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return JS_ToBool(ctx, value); + }; + + static JSValue ToValue(JSContext* ctx, bool value) { return JS_NewBool(ctx, value); }; +}; + +// Uint32 +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + uint32_t v; + JS_ToUint32(ctx, &v, value); + return v; + } + + static JSValue ToValue(JSContext* ctx, uint32_t v) { return JS_NewUint32(ctx, v); } +}; + +// Int32 +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + int32_t v; + JS_ToInt32(ctx, &v, value); + return v; + } + static JSValue ToValue(JSContext* ctx, uint32_t v) { return JS_NewInt32(ctx, v); } +}; + +// Int64 +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + int64_t v; + JS_ToInt64(ctx, &v, value); + return v; + } + static JSValue ToValue(JSContext* ctx, uint32_t v) { return JS_NewInt64(ctx, v); } +}; + +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + double v; + JS_ToFloat64(ctx, &v, value); + return v; + } + + static JSValue ToValue(JSContext* ctx, double v) { return JS_NewFloat64(ctx, v); } +}; + +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return AtomicString(ctx, value); + } + + static JSValue ToValue(JSContext* ctx, const AtomicString& value) { return value.ToQuickJS(ctx); } + static JSValue ToValue(JSContext* ctx, NativeString* str) { + return JS_NewUnicodeString(ctx, str->string(), str->length()); + } + static JSValue ToValue(JSContext* ctx, std::unique_ptr str) { + return JS_NewUnicodeString(ctx, str->string(), str->length()); + } + static JSValue ToValue(JSContext* ctx, uint16_t* bytes, size_t length) { + return JS_NewUnicodeString(ctx, bytes, length); + } + static JSValue ToValue(JSContext* ctx, const std::string& str) { return JS_NewString(ctx, str.c_str()); } +}; + +template <> +struct Converter> : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsUndefined(value)) + return AtomicString::Empty(ctx); + return Converter::FromValue(ctx, value, exception_state); + } + + static JSValue ToValue(JSContext* ctx, uint16_t* bytes, size_t length) { + return Converter::ToValue(ctx, bytes, length); + } + static JSValue ToValue(JSContext* ctx, const std::string& str) { return Converter::ToValue(ctx, str); } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, std::move(value)); + } +}; + +template <> +struct Converter> : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsNull(value)) + return AtomicString::Empty(ctx); + return Converter::FromValue(ctx, value, exception_state); + } + + static JSValue ToValue(JSContext* ctx, const std::string& value) { return AtomicString(ctx, value).ToQuickJS(ctx); } + static JSValue ToValue(JSContext* ctx, const AtomicString& value) { return value.ToQuickJS(ctx); } +}; + +template +struct Converter> : public ConverterBase> { + using ImplType = typename IDLSequence::ImplType>::ImplType; + + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + assert(JS_IsArray(ctx, value)); + + ImplType v; + uint32_t length = Converter::FromValue(ctx, JS_GetPropertyStr(ctx, value, "length"), exception_state); + + v.reserve(length); + + for (uint32_t i = 0; i < length; i++) { + auto&& item = Converter::FromValue(ctx, JS_GetPropertyUint32(ctx, value, i), exception_state); + if (exception_state.HasException()) { + return {}; + } + + v.emplace_back(item); + } + + return v; + } +}; + +template +struct Converter>> : public ConverterBase> { + using ImplType = typename IDLSequence::ImplType>::ImplType; + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsUndefined(value)) { + return {}; + } + + return Converter>::FromValue(ctx, value, exception_state); + } +}; + +template +struct Converter>> : public ConverterBase> { + using ImplType = typename IDLSequence::ImplType>::ImplType; + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsNull(value)) { + return {}; + } + + return Converter>::FromValue(ctx, value, exception_state); + } +}; + +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + if (!JS_IsFunction(ctx, value)) { + return nullptr; + } + + return QJSFunction::Create(ctx, value); + } +}; + +template <> +struct Converter : public ConverterBase { + using ImplType = BlobPart::ImplType; + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return BlobPart::Create(ctx, value, exception_state); + } + + static JSValue ToValue(JSContext* ctx, BlobPart* data) { return data->ToQuickJS(ctx); } +}; + +template <> +struct Converter : public ConverterBase { + using ImplType = BlobPropertyBag::ImplType; + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return BlobPropertyBag::Create(ctx, value, exception_state); + } +}; + +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return JSEventListener::CreateOrNull(QJSFunction::Create(ctx, value)); + } +}; + +template <> +struct Converter> : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsNull(value)) { + return nullptr; + } + + assert(!JS_IsException(value)); + return Converter::FromValue(ctx, value, exception_state); + } +}; + +// DictionaryBase and Derived class. +template +struct Converter::value>> : public ConverterBase { + static typename T::ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return T::Create(ctx, value, exception_state); + } +}; + +// ScriptWrappable and Derived class. +template +struct Converter::value>> : public ConverterBase { + static T* FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return toScriptWrappable(value); + } + static T* ArgumentsValue(ExecutingContext* context, + JSValue value, + uint32_t argv_index, + ExceptionState& exception_state) { + assert(!JS_IsException(value)); + const WrapperTypeInfo* wrapper_type_info = Node::GetStaticWrapperTypeInfo(); + if (JS_IsInstanceOf(context->ctx(), value, context->contextData()->constructorForType(wrapper_type_info))) { + return FromValue(context->ctx(), value, exception_state); + } + exception_state.ThrowException(context->ctx(), ErrorType::TypeError, + ExceptionMessage::ArgumentNotOfType(argv_index, wrapper_type_info->className)); + return nullptr; + } + static JSValue ToValue(JSContext* ctx, T* value) { return value->ToQuickJS(); } + static JSValue ToValue(JSContext* ctx, const T* value) { return value->ToQuickJS(); } +}; + +template <> +struct Converter : public ConverterBase { + static Window* FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + return toScriptWrappable(value); + } + static JSValue ToValue(JSContext* ctx, Window* window) { + return JS_DupValue(ctx, window->GetExecutingContext()->Global()); + } + static JSValue ToValue(JSContext* ctx, const Window* window) { + return JS_DupValue(ctx, window->GetExecutingContext()->Global()); + } +}; + +}; // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/cppgc/garbage_collected.h b/bridge/bindings/qjs/cppgc/garbage_collected.h new file mode 100644 index 0000000000..c0caed2a6d --- /dev/null +++ b/bridge/bindings/qjs/cppgc/garbage_collected.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_GARBAGE_COLLECTED_H +#define KRAKENBRIDGE_GARBAGE_COLLECTED_H + +#include +#include + +#include "bindings/qjs/qjs_engine_patch.h" +#include "foundation/casting.h" +#include "foundation/macros.h" +#include "local_handle.h" + +namespace kraken { + +template +class MakeGarbageCollectedTrait; + +class ExecutingContext; +class GCVisitor; +class ScriptWrappable; + +/** + * This class are mainly designed as base class for ScriptWrappable. If you wants to implement + * a class which have corresponding object in JS environment and have the same memory life circle with JS object, use + * ScriptWrappable instead. + * + * Base class for GC managed objects. Only descendent types of `GarbageCollected` + * can be constructed using `MakeGarbageCollected()`. Must be inherited from as + * left-most base class. + */ +template +class GarbageCollected { + public: + using ParentMostGarbageCollectedType = T; + + // Must use MakeGarbageCollected. + void* operator new(size_t) = delete; + void* operator new[](size_t) = delete; + + /** + * This Trace method must be override by objects inheriting from + * GarbageCollected. + */ + virtual void Trace(GCVisitor* visitor) const = 0; + + virtual void InitializeQuickJSObject(){}; + + protected: + GarbageCollected(){}; + ~GarbageCollected() = default; + friend class MakeGarbageCollectedTrait; +}; + +template +class MakeGarbageCollectedTrait { + public: + template + static T* Allocate(Args&&... args) { + T* object = ::new T(std::forward(args)...); + object->InitializeQuickJSObject(); + return object; + } + + friend GarbageCollected; +}; + +template +T* MakeGarbageCollected(Args&&... args) { + static_assert(std::is_base_of::value, + "MakeGarbageCollected T must be Derived from ScriptWrappable."); + return MakeLocal(MakeGarbageCollectedTrait::Allocate(std::forward(args)...)).Get(); +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_GARBAGE_COLLECTED_H diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.cc b/bridge/bindings/qjs/cppgc/gc_visitor.cc new file mode 100644 index 0000000000..5826531338 --- /dev/null +++ b/bridge/bindings/qjs/cppgc/gc_visitor.cc @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "gc_visitor.h" +#include "bindings/qjs/script_wrappable.h" + +namespace kraken { + +void GCVisitor::Trace(JSValue value) { + JS_MarkValue(runtime_, value, markFunc_); +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.h b/bridge/bindings/qjs/cppgc/gc_visitor.h new file mode 100644 index 0000000000..230e6a736e --- /dev/null +++ b/bridge/bindings/qjs/cppgc/gc_visitor.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_GC_VISITOR_H +#define KRAKENBRIDGE_GC_VISITOR_H + +#include +#include + +#include "foundation/macros.h" +#include "member.h" + +namespace kraken { + +class ScriptWrappable; + +// Use GCVisitor to keep track gc managed members in C++ class. +class GCVisitor final { + KRAKEN_DISALLOW_NEW(); + KRAKEN_DISALLOW_IMPLICIT_CONSTRUCTORS(GCVisitor); + + public: + explicit GCVisitor(JSRuntime* rt, JS_MarkFunc* markFunc) : runtime_(rt), markFunc_(markFunc){}; + + template + void Trace(const Member& target) { + if (target.Get() != nullptr) { + JS_MarkValue(runtime_, target.Get()->jsObject_, markFunc_); + } + }; + + void Trace(JSValue value); + + private: + JSRuntime* runtime_{nullptr}; + JS_MarkFunc* markFunc_{nullptr}; + friend class ScriptWrappable; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_GC_VISITOR_H diff --git a/bridge/bindings/qjs/cppgc/local_handle.h b/bridge/bindings/qjs/cppgc/local_handle.h new file mode 100644 index 0000000000..1f7d85c1ba --- /dev/null +++ b/bridge/bindings/qjs/cppgc/local_handle.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ + +#include +#include +#include "foundation/casting.h" +#include "foundation/macros.h" +#include "mutation_scope.h" + +namespace kraken { + +template +class LocalTrait; +class ScriptWrappable; + +/** + * A stack allocated class which hold object reference temporary. + */ +template +class Local { + KRAKEN_STACK_ALLOCATED(); + + public: + static Local Empty() { return Local(nullptr); } + + Local() = delete; + ~Local(); + + inline T* Get() const { return raw_; } + + protected: + explicit Local(T* p) : raw_(p) { + static_assert(std::is_base_of::value, "Local-Handle only accept ScriptWrappble params."); + }; + + private: + T* raw_; + friend class LocalTrait; +}; + +template +class LocalTrait { + public: + template + static Local Allocate(Args&&... args) { + return Local(std::forward(args)...); + } + + friend class Local; +}; + +template +Local MakeLocal(Args&&... args) { + return LocalTrait::Allocate(std::forward(args)...); +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h new file mode 100644 index 0000000000..20e21c8878 --- /dev/null +++ b/bridge/bindings/qjs/cppgc/member.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ + +#include +#include "bindings/qjs/qjs_engine_patch.h" +#include "bindings/qjs/script_value.h" +#include "bindings/qjs/script_wrappable.h" +#include "foundation/casting.h" +#include "mutation_scope.h" + +namespace kraken { + +class ScriptWrappable; + +/** + * Members are used in classes to contain strong pointers to other garbage + * collected objects. All Member fields of a class must be traced in the class' + * trace method. + */ +template > +class Member { + public: + Member() = default; + Member(T* ptr) { SetRaw(ptr); } + ~Member() { + if (raw_ != nullptr) { + assert(runtime_ != nullptr); + // There are two ways to free the member values: + // One is by GC marking and sweep stage. + // Two is by free directly when running out of function body. + // We detect the GC phase to handle case two, and free our members by hand(call JS_FreeValueRT directly). + JSGCPhaseEnum phase = JS_GetEnginePhase(runtime_); + if (phase == JS_GC_PHASE_DECREF) { + JS_FreeValueRT(runtime_, raw_->ToQuickJSUnsafe()); + } + } + }; + + T* Get() const { return raw_; } + void Clear() { + if (raw_ == nullptr) + return; + auto* wrappable = To(raw_); + // Record the free operation to avoid JSObject had been freed immediately. + wrappable->GetExecutingContext()->mutationScope()->RecordFree(wrappable); + raw_ = nullptr; + } + + // Copy assignment. + Member& operator=(const Member& other) { + operator=(other.Get()); + other.Clear(); + return *this; + } + // Move assignment. + Member& operator=(Member&& other) noexcept { + operator=(other.Get()); + other.Clear(); + return *this; + } + + Member& operator=(T* other) { + Clear(); + SetRaw(other); + return *this; + } + Member& operator=(std::nullptr_t) { + Clear(); + return *this; + } + + explicit operator bool() const { return Get(); } + operator T*() const { return Get(); } + T* operator->() const { return Get(); } + T& operator*() const { return *Get(); } + + private: + void SetRaw(T* p) { + if (p != nullptr) { + auto* wrappable = To(p); + assert_m(wrappable->GetExecutingContext()->HasMutationScope(), + "Member must be used after MemberMutationScope allcated."); + runtime_ = wrappable->runtime(); + JS_DupValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); + } + raw_ = p; + } + + T* raw_{nullptr}; + JSRuntime* runtime_{nullptr}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.cc b/bridge/bindings/qjs/cppgc/mutation_scope.cc new file mode 100644 index 0000000000..1679eff203 --- /dev/null +++ b/bridge/bindings/qjs/cppgc/mutation_scope.cc @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "mutation_scope.h" +#include "core/executing_context.h" + +namespace kraken { + +MemberMutationScope::MemberMutationScope(ExecutingContext* context) : context_(context) { + context->SetMutationScope(*this); +} + +MemberMutationScope::~MemberMutationScope() { + ApplyRecord(); + context_->ClearMutationScope(); +} + +void MemberMutationScope::SetParent(MemberMutationScope* parent_scope) { + assert(parent_scope_ == nullptr); + parent_scope_ = parent_scope; +} + +MemberMutationScope* MemberMutationScope::Parent() const { + return parent_scope_; +} + +void MemberMutationScope::RecordFree(ScriptWrappable* wrappable) { + if (mutation_records_.count(wrappable) == 0) { + mutation_records_.insert(std::make_pair(wrappable, 0)); + } + mutation_records_[wrappable]--; +} + +void MemberMutationScope::ApplyRecord() { + JSContext* ctx = context_->ctx(); + for (auto& entry : mutation_records_) { + for (int i = 0; i < -entry.second; i++) { + JS_FreeValue(ctx, entry.first->ToQuickJSUnsafe()); + } + } +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.h b/bridge/bindings/qjs/cppgc/mutation_scope.h new file mode 100644 index 0000000000..14fefbc593 --- /dev/null +++ b/bridge/bindings/qjs/cppgc/mutation_scope.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ + +#include +#include +#include "foundation/macros.h" + +namespace kraken { + +class ExecutingContext; +class ScriptWrappable; + +/** + * A stack-allocated class that record all members mutations in stack scope. + */ +class MemberMutationScope { + KRAKEN_DISALLOW_NEW(); + + public: + MemberMutationScope() = delete; + explicit MemberMutationScope(ExecutingContext* context); + ~MemberMutationScope(); + + void SetParent(MemberMutationScope* parent_scope); + [[nodiscard]] MemberMutationScope* Parent() const; + + void RecordFree(ScriptWrappable* wrappable); + + private: + void ApplyRecord(); + + MemberMutationScope* parent_scope_{nullptr}; + ExecutingContext* context_; + std::unordered_map mutation_records_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ diff --git a/bridge/bindings/qjs/dictionary_base.cc b/bridge/bindings/qjs/dictionary_base.cc new file mode 100644 index 0000000000..bc2ff2c4fe --- /dev/null +++ b/bridge/bindings/qjs/dictionary_base.cc @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2022 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "dictionary_base.h" + +namespace kraken { + +JSValue DictionaryBase::toQuickJS(JSContext* ctx) const { + JSValue object = JS_NewObject(ctx); + if (!FillQJSObjectWithMembers(ctx, object)) { + return JS_NULL; + } + return object; +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/dictionary_base.h b/bridge/bindings/qjs/dictionary_base.h new file mode 100644 index 0000000000..e1e75631b9 --- /dev/null +++ b/bridge/bindings/qjs/dictionary_base.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ + +#include "bindings/qjs/cppgc/garbage_collected.h" + +namespace kraken { + +// DictionaryBase is the common base class of all the IDL dictionary classes. +// Most importantly this class provides a way of type dispatching (e.g. overload +// resolutions, SFINAE technique, etc.) so that it's possible to distinguish +// IDL dictionaries from anything else. Also it provides a common +// implementation of IDL dictionaries. +class DictionaryBase { + public: + virtual ~DictionaryBase() = default; + + JSValue toQuickJS(JSContext* ctx) const; + + protected: + DictionaryBase() = default; + + DictionaryBase(const DictionaryBase&) = delete; + DictionaryBase(const DictionaryBase&&) = delete; + DictionaryBase& operator=(const DictionaryBase&) = delete; + DictionaryBase& operator=(const DictionaryBase&&) = delete; + + // Fills the given QuickJS object with the dictionary members. Returns true on + // success, otherwise returns false with throwing an exception. + virtual bool FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const = 0; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ diff --git a/bridge/bindings/qjs/dom/all_collection.cc b/bridge/bindings/qjs/dom/all_collection.cc deleted file mode 100644 index dcb1891c6b..0000000000 --- a/bridge/bindings/qjs/dom/all_collection.cc +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "all_collection.h" - -namespace kraken::binding::qjs { - -JSValue AllCollection::item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_NULL; - } - - uint32_t index; - JS_ToUint32(ctx, &index, argv[0]); - auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - - if (index >= collection->m_nodes.size()) { - return JS_NULL; - } - - auto node = collection->m_nodes[index]; - return node->jsObject; -} -JSValue AllCollection::add(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: 1 arguments required."); - } - - if (!JS_IsObject(argv[0])) { - return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: first arguments should be a object."); - } - - JSValue before = JS_NULL; - - if (argc == 2 && JS_IsObject(argv[1])) { - before = argv[1]; - } - - auto* node = static_cast(JS_GetOpaque(argv[0], ExecutionContext::kHostObjectClassId)); - auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - NodeInstance* beforeNode = nullptr; - - if (!JS_IsNull(before)) { - beforeNode = static_cast(JS_GetOpaque(before, ExecutionContext::kHostObjectClassId)); - } - - collection->internalAdd(node, beforeNode); - - return JS_NULL; -} -JSValue AllCollection::remove(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute remove() on HTMLAllCollection: 1 arguments required."); - } - - uint32_t index; - JS_ToUint32(ctx, &index, argv[0]); - auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - collection->m_nodes.erase(collection->m_nodes.begin() + index); - return JS_NULL; -} -void AllCollection::internalAdd(NodeInstance* node, NodeInstance* before) { - if (before != nullptr) { - auto it = std::find(m_nodes.begin(), m_nodes.end(), before); - m_nodes.erase(it); - m_nodes.insert(it, node); - } else { - m_nodes.emplace_back(node); - } -} - -IMPL_PROPERTY_GETTER(AllCollection, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewUint32(ctx, collection->m_nodes.size()); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/all_collection.h b/bridge/bindings/qjs/dom/all_collection.h deleted file mode 100644 index 1a6374334b..0000000000 --- a/bridge/bindings/qjs/dom/all_collection.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_ALL_COLLECTION_H -#define KRAKENBRIDGE_ALL_COLLECTION_H - -#include "bindings/qjs/host_object.h" -#include "node.h" - -namespace kraken::binding::qjs { - -class AllCollection : public HostObject { - public: - AllCollection(ExecutionContext* context) : HostObject(context, "AllCollection"){}; - - static JSValue item(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue add(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue remove(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - DEFINE_READONLY_PROPERTY(length); - - void internalAdd(NodeInstance* node, NodeInstance* before); - - private: - std::vector m_nodes; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_ALL_COLLECTION_H diff --git a/bridge/bindings/qjs/dom/comment_node.cc b/bridge/bindings/qjs/dom/comment_node.cc deleted file mode 100644 index 62274c1ed6..0000000000 --- a/bridge/bindings/qjs/dom/comment_node.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "comment_node.h" -#include "document.h" -#include "kraken_bridge.h" - -namespace kraken::binding::qjs { - -std::once_flag kCommentInitFlag; - -JSClassID Comment::kCommentClassId{0}; - -void bindCommentNode(ExecutionContext* context) { - auto* constructor = Comment::instance(context); - context->defineGlobalProperty("Comment", constructor->jsObject); -} - -JSClassID Comment::classId() { - return kCommentClassId; -} - -Comment::Comment(ExecutionContext* context) : Node(context, "Comment") { - std::call_once(kCommentInitFlag, []() { JS_NewClassID(&kCommentClassId); }); - JS_SetPrototype(m_ctx, m_prototypeObject, Node::instance(m_context)->prototype()); -} - -JSValue Comment::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - return (new CommentInstance(this))->jsObject; -} - -IMPL_PROPERTY_GETTER(Comment, data)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NewString(ctx, ""); -} - -IMPL_PROPERTY_GETTER(Comment, nodeName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NewString(ctx, "#comment"); -} - -IMPL_PROPERTY_GETTER(Comment, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NewUint32(ctx, 0); -} - -CommentInstance::CommentInstance(Comment* comment) : NodeInstance(comment, NodeType::COMMENT_NODE, Comment::classId(), "Comment") { - m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createComment, nativeEventTarget); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/comment_node.h b/bridge/bindings/qjs/dom/comment_node.h deleted file mode 100644 index 42d35329c7..0000000000 --- a/bridge/bindings/qjs/dom/comment_node.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_COMMENT_NODE_H -#define KRAKENBRIDGE_COMMENT_NODE_H - -#include "node.h" - -namespace kraken::binding::qjs { - -void bindCommentNode(ExecutionContext* context); - -class CommentInstance; - -class Comment : public Node { - public: - static JSClassID kCommentClassId; - static JSClassID classId(); - Comment() = delete; - explicit Comment(ExecutionContext* context); - - OBJECT_INSTANCE(Comment); - - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - private: - DEFINE_PROTOTYPE_READONLY_PROPERTY(data); - DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); - DEFINE_PROTOTYPE_READONLY_PROPERTY(length); - - friend CommentInstance; -}; - -class CommentInstance : public NodeInstance { - public: - CommentInstance() = delete; - explicit CommentInstance(Comment* comment); - - private: - friend Comment; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_COMMENT_NODE_H diff --git a/bridge/bindings/qjs/dom/css_property_list.h b/bridge/bindings/qjs/dom/css_property_list.h deleted file mode 100644 index a4cf27faf3..0000000000 --- a/bridge/bindings/qjs/dom/css_property_list.h +++ /dev/null @@ -1,435 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_BINDINGS_QJS_DOM_CSS_PROPERTY_LIST_H_ -#define KRAKENBRIDGE_BINDINGS_QJS_DOM_CSS_PROPERTY_LIST_H_ - -#include -#include - -namespace kraken { - -std::unordered_map cssPropertyList{{"accentColor", true}, - {"additiveSymbols", true}, - {"alignContent", true}, - {"alignItems", true}, - {"alignSelf", true}, - {"alignmentBaseline", true}, - {"all", true}, - {"animation", true}, - {"animationDelay", true}, - {"animationDirection", true}, - {"animationDuration", true}, - {"animationFillMode", true}, - {"animationIterationCount", true}, - {"animationName", true}, - {"animationPlayState", true}, - {"animationTimingFunction", true}, - {"appRegion", true}, - {"appearance", true}, - {"ascentOverride", true}, - {"aspectRatio", true}, - {"backdropFilter", true}, - {"backfaceVisibility", true}, - {"background", true}, - {"backgroundAttachment", true}, - {"backgroundBlendMode", true}, - {"backgroundClip", true}, - {"backgroundColor", true}, - {"backgroundImage", true}, - {"backgroundOrigin", true}, - {"backgroundPosition", true}, - {"backgroundPositionX", true}, - {"backgroundPositionY", true}, - {"backgroundRepeat", true}, - {"backgroundRepeatX", true}, - {"backgroundRepeatY", true}, - {"backgroundSize", true}, - {"baselineShift", true}, - {"blockSize", true}, - {"border", true}, - {"borderBlock", true}, - {"borderBlockColor", true}, - {"borderBlockEnd", true}, - {"borderBlockEndColor", true}, - {"borderBlockEndStyle", true}, - {"borderBlockEndWidth", true}, - {"borderBlockStart", true}, - {"borderBlockStartColor", true}, - {"borderBlockStartStyle", true}, - {"borderBlockStartWidth", true}, - {"borderBlockStyle", true}, - {"borderBlockWidth", true}, - {"borderBottom", true}, - {"borderBottomColor", true}, - {"borderBottomLeftRadius", true}, - {"borderBottomRightRadius", true}, - {"borderBottomStyle", true}, - {"borderBottomWidth", true}, - {"borderCollapse", true}, - {"borderColor", true}, - {"borderEndEndRadius", true}, - {"borderEndStartRadius", true}, - {"borderImage", true}, - {"borderImageOutset", true}, - {"borderImageRepeat", true}, - {"borderImageSlice", true}, - {"borderImageSource", true}, - {"borderImageWidth", true}, - {"borderInline", true}, - {"borderInlineColor", true}, - {"borderInlineEnd", true}, - {"borderInlineEndColor", true}, - {"borderInlineEndStyle", true}, - {"borderInlineEndWidth", true}, - {"borderInlineStart", true}, - {"borderInlineStartColor", true}, - {"borderInlineStartStyle", true}, - {"borderInlineStartWidth", true}, - {"borderInlineStyle", true}, - {"borderInlineWidth", true}, - {"borderLeft", true}, - {"borderLeftColor", true}, - {"borderLeftStyle", true}, - {"borderLeftWidth", true}, - {"borderRadius", true}, - {"borderRight", true}, - {"borderRightColor", true}, - {"borderRightStyle", true}, - {"borderRightWidth", true}, - {"borderSpacing", true}, - {"borderStartEndRadius", true}, - {"borderStartStartRadius", true}, - {"borderStyle", true}, - {"borderTop", true}, - {"borderTopColor", true}, - {"borderTopLeftRadius", true}, - {"borderTopRightRadius", true}, - {"borderTopStyle", true}, - {"borderTopWidth", true}, - {"borderWidth", true}, - {"bottom", true}, - {"boxShadow", true}, - {"boxSizing", true}, - {"breakAfter", true}, - {"breakBefore", true}, - {"breakInside", true}, - {"bufferedRendering", true}, - {"captionSide", true}, - {"caretColor", true}, - {"clear", true}, - {"clip", true}, - {"clipPath", true}, - {"clipRule", true}, - {"color", true}, - {"colorInterpolation", true}, - {"colorInterpolationFilters", true}, - {"colorRendering", true}, - {"colorScheme", true}, - {"columnCount", true}, - {"columnFill", true}, - {"columnGap", true}, - {"columnRule", true}, - {"columnRuleColor", true}, - {"columnRuleStyle", true}, - {"columnRuleWidth", true}, - {"columnSpan", true}, - {"columnWidth", true}, - {"columns", true}, - {"content", true}, - {"contentVisibility", true}, - {"counterIncrement", true}, - {"counterReset", true}, - {"counterSet", true}, - {"cursor", true}, - {"cx", true}, - {"cy", true}, - {"d", true}, - {"descentOverride", true}, - {"direction", true}, - {"display", true}, - {"dominantBaseline", true}, - {"emptyCells", true}, - {"fallback", true}, - {"fill", true}, - {"fillOpacity", true}, - {"fillRule", true}, - {"filter", true}, - {"flex", true}, - {"flexBasis", true}, - {"flexDirection", true}, - {"flexFlow", true}, - {"flexGrow", true}, - {"flexShrink", true}, - {"flexWrap", true}, - {"float", true}, - {"floodColor", true}, - {"floodOpacity", true}, - {"font", true}, - {"fontDisplay", true}, - {"fontFamily", true}, - {"fontFeatureSettings", true}, - {"fontKerning", true}, - {"fontOpticalSizing", true}, - {"fontSize", true}, - {"fontStretch", true}, - {"fontStyle", true}, - {"fontSynthesis", true}, - {"fontSynthesisSmallCaps", true}, - {"fontSynthesisStyle", true}, - {"fontSynthesisWeight", true}, - {"fontVariant", true}, - {"fontVariantCaps", true}, - {"fontVariantEastAsian", true}, - {"fontVariantLigatures", true}, - {"fontVariantNumeric", true}, - {"fontVariationSettings", true}, - {"fontWeight", true}, - {"forcedColorAdjust", true}, - {"gap", true}, - {"grid", true}, - {"gridArea", true}, - {"gridAutoColumns", true}, - {"gridAutoFlow", true}, - {"gridAutoRows", true}, - {"gridColumn", true}, - {"gridColumnEnd", true}, - {"gridColumnGap", true}, - {"gridColumnStart", true}, - {"gridGap", true}, - {"gridRow", true}, - {"gridRowEnd", true}, - {"gridRowGap", true}, - {"gridRowStart", true}, - {"gridTemplate", true}, - {"gridTemplateAreas", true}, - {"gridTemplateColumns", true}, - {"gridTemplateRows", true}, - {"height", true}, - {"hyphens", true}, - {"imageOrientation", true}, - {"imageRendering", true}, - {"inherits", true}, - {"initialValue", true}, - {"inlineSize", true}, - {"inset", true}, - {"insetBlock", true}, - {"insetBlockEnd", true}, - {"insetBlockStart", true}, - {"insetInline", true}, - {"insetInlineEnd", true}, - {"insetInlineStart", true}, - {"isolation", true}, - {"justifyContent", true}, - {"justifyItems", true}, - {"justifySelf", true}, - {"left", true}, - {"letterSpacing", true}, - {"lightingColor", true}, - {"lineBreak", true}, - {"lineGapOverride", true}, - {"lineHeight", true}, - {"listStyle", true}, - {"listStyleImage", true}, - {"listStylePosition", true}, - {"listStyleType", true}, - {"margin", true}, - {"marginBlock", true}, - {"marginBlockEnd", true}, - {"marginBlockStart", true}, - {"marginBottom", true}, - {"marginInline", true}, - {"marginInlineEnd", true}, - {"marginInlineStart", true}, - {"marginLeft", true}, - {"marginRight", true}, - {"marginTop", true}, - {"marker", true}, - {"markerEnd", true}, - {"markerMid", true}, - {"markerStart", true}, - {"mask", true}, - {"maskType", true}, - {"maxBlockSize", true}, - {"maxHeight", true}, - {"maxInlineSize", true}, - {"maxWidth", true}, - {"maxZoom", true}, - {"minBlockSize", true}, - {"minHeight", true}, - {"minInlineSize", true}, - {"minWidth", true}, - {"minZoom", true}, - {"mixBlendMode", true}, - {"negative", true}, - {"objectFit", true}, - {"objectPosition", true}, - {"offset", true}, - {"offsetDistance", true}, - {"offsetPath", true}, - {"offsetRotate", true}, - {"opacity", true}, - {"order", true}, - {"orientation", true}, - {"orphans", true}, - {"outline", true}, - {"outlineColor", true}, - {"outlineOffset", true}, - {"outlineStyle", true}, - {"outlineWidth", true}, - {"overflow", true}, - {"overflowAnchor", true}, - {"overflowClipMargin", true}, - {"overflowWrap", true}, - {"overflowX", true}, - {"overflowY", true}, - {"overscrollBehavior", true}, - {"overscrollBehaviorBlock", true}, - {"overscrollBehaviorInline", true}, - {"overscrollBehaviorX", true}, - {"overscrollBehaviorY", true}, - {"pad", true}, - {"padding", true}, - {"paddingBlock", true}, - {"paddingBlockEnd", true}, - {"paddingBlockStart", true}, - {"paddingBottom", true}, - {"paddingInline", true}, - {"paddingInlineEnd", true}, - {"paddingInlineStart", true}, - {"paddingLeft", true}, - {"paddingRight", true}, - {"paddingTop", true}, - {"page", true}, - {"pageBreakAfter", true}, - {"pageBreakBefore", true}, - {"pageBreakInside", true}, - {"pageOrientation", true}, - {"paintOrder", true}, - {"perspective", true}, - {"perspectiveOrigin", true}, - {"placeContent", true}, - {"placeItems", true}, - {"placeSelf", true}, - {"pointerEvents", true}, - {"position", true}, - {"prefix", true}, - {"quotes", true}, - {"r", true}, - {"range", true}, - {"resize", true}, - {"right", true}, - {"rowGap", true}, - {"rubyPosition", true}, - {"rx", true}, - {"ry", true}, - {"scrollBehavior", true}, - {"scrollMargin", true}, - {"scrollMarginBlock", true}, - {"scrollMarginBlockEnd", true}, - {"scrollMarginBlockStart", true}, - {"scrollMarginBottom", true}, - {"scrollMarginInline", true}, - {"scrollMarginInlineEnd", true}, - {"scrollMarginInlineStart", true}, - {"scrollMarginLeft", true}, - {"scrollMarginRight", true}, - {"scrollMarginTop", true}, - {"scrollPadding", true}, - {"scrollPaddingBlock", true}, - {"scrollPaddingBlockEnd", true}, - {"scrollPaddingBlockStart", true}, - {"scrollPaddingBottom", true}, - {"scrollPaddingInline", true}, - {"scrollPaddingInlineEnd", true}, - {"scrollPaddingInlineStart", true}, - {"scrollPaddingLeft", true}, - {"scrollPaddingRight", true}, - {"scrollPaddingTop", true}, - {"scrollSnapAlign", true}, - {"scrollSnapStop", true}, - {"scrollSnapType", true}, - {"scrollbarGutter", true}, - {"shapeImageThreshold", true}, - {"shapeMargin", true}, - {"shapeOutside", true}, - {"shapeRendering", true}, - {"size", true}, - {"sizeAdjust", true}, - {"speak", true}, - {"speakAs", true}, - {"src", true}, - {"stopColor", true}, - {"stopOpacity", true}, - {"stroke", true}, - {"strokeDasharray", true}, - {"strokeDashoffset", true}, - {"strokeLinecap", true}, - {"strokeLinejoin", true}, - {"strokeMiterlimit", true}, - {"strokeOpacity", true}, - {"strokeWidth", true}, - {"suffix", true}, - {"symbols", true}, - {"syntax", true}, - {"system", true}, - {"tabSize", true}, - {"tableLayout", true}, - {"textAlign", true}, - {"textAlignLast", true}, - {"textAnchor", true}, - {"textCombineUpright", true}, - {"textDecoration", true}, - {"textDecorationColor", true}, - {"textDecorationLine", true}, - {"textDecorationSkipInk", true}, - {"textDecorationStyle", true}, - {"textDecorationThickness", true}, - {"textEmphasis", true}, - {"textEmphasisColor", true}, - {"textEmphasisPosition", true}, - {"textEmphasisStyle", true}, - {"textIndent", true}, - {"textOrientation", true}, - {"textOverflow", true}, - {"textRendering", true}, - {"textShadow", true}, - {"textSizeAdjust", true}, - {"textTransform", true}, - {"textUnderlineOffset", true}, - {"textUnderlinePosition", true}, - {"top", true}, - {"touchAction", true}, - {"transform", true}, - {"transformBox", true}, - {"transformOrigin", true}, - {"transformStyle", true}, - {"transition", true}, - {"transitionDelay", true}, - {"transitionDuration", true}, - {"transitionProperty", true}, - {"transitionTimingFunction", true}, - {"unicodeBidi", true}, - {"unicodeRange", true}, - {"userSelect", true}, - {"userZoom", true}, - {"vectorEffect", true}, - {"verticalAlign", true}, - {"visibility", true}, - {"whiteSpace", true}, - {"widows", true}, - {"width", true}, - {"willChange", true}, - {"wordBreak", true}, - {"wordSpacing", true}, - {"wordWrap", true}, - {"writingMode", true}, - {"x", true}, - {"y", true}, - {"zIndex", true}, - {"zoom", true}}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_BINDINGS_QJS_DOM_CSS_PROPERTY_LIST_H_ diff --git a/bridge/bindings/qjs/dom/custom_event.cc b/bridge/bindings/qjs/dom/custom_event.cc deleted file mode 100644 index 2fa39bc93b..0000000000 --- a/bridge/bindings/qjs/dom/custom_event.cc +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "custom_event.h" -#include "bindings/qjs/qjs_patch.h" -#include "kraken_bridge.h" - -#include - -namespace kraken::binding::qjs { - -void bindCustomEvent(ExecutionContext* context) { - auto* constructor = CustomEvent::instance(context); - context->defineGlobalProperty("CustomEvent", constructor->jsObject); -} - -JSValue CustomEvent::initCustomEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'initCustomEvent' on 'CustomEvent': 1 argument required, but only 0 present"); - } - - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - if (eventInstance == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to addEventListener: this is not an EventTarget object."); - } - - JSValue typeValue = argv[0]; - eventInstance->setType(jsValueToNativeString(ctx, typeValue).release()); - - if (argc <= 2) { - bool canBubble = JS_ToBool(ctx, argv[1]); - eventInstance->nativeEvent->bubbles = canBubble ? 1 : 0; - } - - if (argc <= 3) { - bool cancelable = JS_ToBool(ctx, argv[2]); - eventInstance->nativeEvent->cancelable = cancelable ? 1 : 0; - } - - if (argc <= 4) { - eventInstance->m_detail.value(argv[3]); - } - return JS_NULL; -} - -JSValue CustomEvent::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to construct 'CustomEvent': 1 argument required, but only 0 present."); - } - - JSValue typeArgsValue = argv[0]; - JSValue customEventInit = JS_NULL; - - if (argc == 2) { - customEventInit = argv[1]; - } - - JSAtom typeAtom = JS_ValueToAtom(m_ctx, typeArgsValue); - auto* customEvent = new CustomEventInstance(CustomEvent::instance(context()), typeAtom, customEventInit); - JS_FreeAtom(m_ctx, typeAtom); - - return customEvent->jsObject; -} - -IMPL_PROPERTY_GETTER(CustomEvent, detail)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* customEventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - return customEventInstance->m_detail.value(); -} - -CustomEventInstance::CustomEventInstance(CustomEvent* jsCustomEvent, JSAtom customEventType, JSValue eventInit) : EventInstance(jsCustomEvent, customEventType, eventInit) { - if (!JS_IsNull(eventInit)) { - JSAtom detailKey = JS_NewAtom(m_ctx, "detail"); - if (JS_HasProperty(m_ctx, eventInit, detailKey)) { - JSValue detailValue = JS_GetProperty(m_ctx, eventInit, detailKey); - m_detail.value(detailValue); - JS_FreeValue(m_ctx, detailValue); - } - JS_FreeAtom(m_ctx, detailKey); - } -} - -CustomEventInstance::CustomEventInstance(CustomEvent* jsCustomEvent, NativeCustomEvent* nativeCustomEvent) - : nativeCustomEvent(nativeCustomEvent), EventInstance(jsCustomEvent, reinterpret_cast(nativeCustomEvent)) { - auto* detail = reinterpret_cast(nativeCustomEvent->detail); - JSValue newDetail = JS_NewUnicodeString(jsCustomEvent->context()->runtime(), jsCustomEvent->context()->ctx(), detail->string, detail->length); - detail->free(); - m_detail.value(newDetail); - JS_FreeValue(m_ctx, newDetail); -} -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/custom_event.h b/bridge/bindings/qjs/dom/custom_event.h deleted file mode 100644 index 80a1b09ce5..0000000000 --- a/bridge/bindings/qjs/dom/custom_event.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_CUSTOM_EVENT_H -#define KRAKENBRIDGE_CUSTOM_EVENT_H - -#include "event.h" - -namespace kraken::binding::qjs { - -void bindCustomEvent(ExecutionContext* context); - -struct NativeCustomEvent { - NativeEvent nativeEvent; - int64_t detail{0}; -}; - -class CustomEventInstance; - -class CustomEvent : public Event { - public: - CustomEvent() = delete; - explicit CustomEvent(ExecutionContext* context) : Event(context) { JS_SetPrototype(m_ctx, m_prototypeObject, Event::instance(m_context)->prototype()); }; - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - static JSValue initCustomEvent(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - OBJECT_INSTANCE(CustomEvent); - - private: - DEFINE_PROTOTYPE_READONLY_PROPERTY(detail); - - DEFINE_PROTOTYPE_FUNCTION(initCustomEvent, 4); - friend CustomEventInstance; -}; - -class CustomEventInstance : public EventInstance { - public: - explicit CustomEventInstance(CustomEvent* jsCustomEvent, JSAtom CustomEventType, JSValue eventInit); - explicit CustomEventInstance(CustomEvent* jsCustomEvent, NativeCustomEvent* nativeCustomEvent); - - private: - JSValueHolder m_detail{m_ctx, JS_NULL}; - NativeCustomEvent* nativeCustomEvent{nullptr}; - friend CustomEvent; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_CUSTOM_EVENT_H diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/bindings/qjs/dom/document.cc deleted file mode 100644 index 459029c9b9..0000000000 --- a/bridge/bindings/qjs/dom/document.cc +++ /dev/null @@ -1,612 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "document.h" -#include -#include "all_collection.h" -#include "bindings/qjs/executing_context.h" -#include "comment_node.h" -#include "dart_methods.h" -#include "document_fragment.h" -#include "element.h" -#include "event.h" -#include "text_node.h" - -#include "bindings/qjs/dom/elements/image_element.h" -#include "elements/.gen/anchor_element.h" -#include "elements/.gen/canvas_element.h" -#include "elements/.gen/input_element.h" -#include "elements/.gen/object_element.h" -#include "elements/.gen/script_element.h" -#include "elements/.gen/textarea_element.h" -#include "elements/template_element.h" - -#include "events/.gen/close_event.h" -#include "events/.gen/gesture_event.h" -#include "events/.gen/input_event.h" -#include "events/.gen/intersection_change.h" -#include "events/.gen/media_error_event.h" -#include "events/.gen/message_event.h" -#include "events/.gen/mouse_event.h" -#include "events/.gen/popstate_event.h" -#include "events/touch_event.h" - -namespace kraken::binding::qjs { - -void traverseNode(NodeInstance* node, TraverseHandler handler) { - bool shouldExit = handler(node); - if (shouldExit) - return; - - JSContext* ctx = node->context()->ctx(); - int childNodesLen = arrayGetLength(ctx, node->childNodes); - - if (childNodesLen != 0) { - for (int i = 0; i < childNodesLen; i++) { - JSValue n = JS_GetPropertyUint32(ctx, node->childNodes, i); - auto* nextNode = static_cast(JS_GetOpaque(n, Node::classId(n))); - traverseNode(nextNode, handler); - - JS_FreeValue(node->context()->ctx(), n); - } - } -} - -std::once_flag kDocumentInitOnceFlag; - -void bindDocument(ExecutionContext* context) { - auto* documentConstructor = Document::instance(context); - context->defineGlobalProperty("Document", documentConstructor->jsObject); - JSValue documentInstance = JS_CallConstructor(context->ctx(), documentConstructor->jsObject, 0, nullptr); - context->defineGlobalProperty("document", documentInstance); -} - -JSClassID Document::kDocumentClassID{0}; - -Document::Document(ExecutionContext* context) : Node(context, "Document") { - std::call_once(kDocumentInitOnceFlag, []() { JS_NewClassID(&kDocumentClassID); }); - JS_SetPrototype(m_ctx, m_prototypeObject, Node::instance(m_context)->prototype()); - if (!document_registered) { - defineElement("img", ImageElement::instance(m_context)); - defineElement("a", AnchorElement::instance(m_context)); - defineElement("canvas", CanvasElement::instance(m_context)); - defineElement("input", InputElement::instance(m_context)); - defineElement("textarea", TextareaElement::instance(m_context)); - defineElement("object", ObjectElement::instance(m_context)); - defineElement("script", ScriptElement::instance(m_context)); - defineElement("template", TemplateElement::instance(m_context)); - document_registered = true; - } - - if (!event_registered) { - event_registered = true; - Event::defineEvent( - EVENT_INPUT, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { return new InputEventInstance(InputEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_MEDIA_ERROR, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new MediaErrorEventInstance(MediaErrorEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - Event::defineEvent(EVENT_MESSAGE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new MessageEventInstance(MessageEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - Event::defineEvent( - EVENT_CLOSE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { return new CloseEventInstance(CloseEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_INTERSECTION_CHANGE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new IntersectionChangeEventInstance(IntersectionChangeEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - Event::defineEvent(EVENT_TOUCH_START, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new TouchEventInstance(TouchEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - Event::defineEvent(EVENT_TOUCH_END, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new TouchEventInstance(TouchEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - Event::defineEvent(EVENT_TOUCH_MOVE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new TouchEventInstance(TouchEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - Event::defineEvent(EVENT_TOUCH_CANCEL, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new TouchEventInstance(TouchEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - Event::defineEvent(EVENT_SWIPE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new GestureEventInstance(GestureEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - Event::defineEvent(EVENT_PAN, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new GestureEventInstance(GestureEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - Event::defineEvent(EVENT_LONG_PRESS, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new GestureEventInstance(GestureEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - Event::defineEvent(EVENT_SCALE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new GestureEventInstance(GestureEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - Event::defineEvent( - EVENT_CLICK, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { return new MouseEventInstance(MouseEvent::instance(context), reinterpret_cast(nativeEvent)); }); - Event::defineEvent(EVENT_CANCEL, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new MouseEventInstance(MouseEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - Event::defineEvent(EVENT_POPSTATE, [](ExecutionContext* context, void* nativeEvent) -> EventInstance* { - return new PopStateEventInstance(PopStateEvent::instance(context), reinterpret_cast(nativeEvent)); - }); - } -} - -JSClassID Document::classId() { - return kDocumentClassID; -} - -JSValue Document::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - auto* instance = new DocumentInstance(this); - return instance->jsObject; -} - -JSValue Document::createEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to argumentCount: 1 argument required, but only 0 present."); - } - - JSValue eventTypeValue = argv[0]; - if (!JS_IsString(eventTypeValue)) { - return JS_ThrowTypeError(ctx, "Failed to createEvent: type should be a string."); - } - const char* c_eventType = JS_ToCString(ctx, eventTypeValue); - JS_FreeCString(ctx, c_eventType); - std::string eventType = std::string(c_eventType); - if (eventType == "Event") { - std::unique_ptr nativeEventType = jsValueToNativeString(ctx, eventTypeValue); -#if ANDROID_32_BIT - auto nativeEvent = new NativeEvent{reinterpret_cast(nativeEventType.release())}; -#else - auto nativeEvent = new NativeEvent{nativeEventType.release()}; -#endif - - auto document = static_cast(JS_GetOpaque(this_val, Document::classId())); - auto e = Event::buildEventInstance(eventType, document->context(), nativeEvent, false); - return e->jsObject; - } else { - return JS_NULL; - } -} - -JSValue Document::createElement(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to createElement: 1 argument required, but only 0 present."); - } - - JSValue tagNameValue = argv[0]; - if (!JS_IsString(tagNameValue)) { - return JS_ThrowTypeError(ctx, "Failed to createElement: tagName should be a string."); - } - - auto document = static_cast(JS_GetOpaque(this_val, Document::classId())); - auto* context = static_cast(JS_GetContextOpaque(ctx)); - std::string tagName = jsValueToStdString(ctx, tagNameValue); - JSValue constructor = static_cast(document->prototype())->getElementConstructor(document->m_context, tagName); - - JSValue element = JS_CallConstructor(ctx, constructor, argc, argv); - return element; -} - -JSValue Document::createTextNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc != 1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'createTextNode' on 'Document': 1 argument required, but only 0 present."); - } - - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - JSValue textNode = JS_CallConstructor(ctx, TextNode::instance(document->m_context)->jsObject, argc, argv); - return textNode; -} - -JSValue Document::createDocumentFragment(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - return JS_CallConstructor(ctx, DocumentFragment::instance(document->m_context)->jsObject, 0, nullptr); -} - -JSValue Document::createComment(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - JSValue commentNode = JS_CallConstructor(ctx, Comment::instance(document->m_context)->jsObject, argc, argv); - return commentNode; -} - -JSValue Document::getElementById(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'getElementById' on 'Document': 1 argument required, but only 0 present."); - } - - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - JSValue idValue = argv[0]; - - if (!JS_IsString(idValue)) - return JS_NULL; - - JSAtom id = JS_ValueToAtom(ctx, idValue); - - if (document->m_elementMapById.count(id) == 0) { - JS_FreeAtom(ctx, id); - return JS_NULL; - }; - - auto targetElementList = document->m_elementMapById[id]; - JS_FreeAtom(ctx, id); - - if (targetElementList.empty()) - return JS_NULL; - - for (auto& element : targetElementList) { - if (element->isConnected()) - return JS_DupValue(ctx, element->jsObject); - } - - return JS_NULL; -} - -JSValue Document::getElementsByTagName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, - "Uncaught TypeError: Failed to execute 'getElementsByTagName' on 'Document': 1 argument required, " - "but only 0 present."); - } - - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - JSValue tagNameValue = argv[0]; - std::string tagName = jsValueToStdString(ctx, tagNameValue); - std::transform(tagName.begin(), tagName.end(), tagName.begin(), ::toupper); - - std::vector elements; - - traverseNode(document, [tagName, &elements](NodeInstance* node) { - if (node->nodeType == NodeType::ELEMENT_NODE) { - auto* element = static_cast(node); - if (element->tagName() == tagName || tagName == "*") { - elements.emplace_back(element); - } - } - - return false; - }); - - JSValue array = JS_NewArray(ctx); - JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); - - for (auto& element : elements) { - JS_Call(ctx, pushMethod, array, 1, &element->jsObject); - } - - JS_FreeValue(ctx, pushMethod); - return array; -} - -JSValue Document::getElementsByClassName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'getElementsByClassName' on 'Document': 1 argument required, but only 0 present."); - } - - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - std::string className = jsValueToStdString(ctx, argv[0]); - - std::vector elements; - traverseNode(document, [ctx, className, &elements](NodeInstance* node) { - if (node->nodeType == NodeType::ELEMENT_NODE) { - auto element = reinterpret_cast(node); - if (element->classNames()->containsAll(className)) { - elements.emplace_back(element); - } - } - - return false; - }); - - JSValue array = JS_NewArray(ctx); - JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); - - for (auto& element : elements) { - JS_Call(ctx, pushMethod, array, 1, &element->jsObject); - } - - JS_FreeValue(ctx, pushMethod); - return array; -} - -void Document::defineElement(const std::string& tagName, Element* constructor) { - elementConstructorMap[tagName] = constructor; -} - -JSValue Document::getElementConstructor(ExecutionContext* context, const std::string& tagName) { - if (elementConstructorMap.count(tagName) > 0) - return elementConstructorMap[tagName]->jsObject; - return Element::instance(context)->jsObject; -} - -bool Document::isCustomElement(const std::string& tagName) { - return elementConstructorMap.count(tagName) > 0; -} - -IMPL_PROPERTY_GETTER(Document, location)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - return JS_GetPropertyStr(ctx, document->m_context->global(), "location"); -} - -IMPL_PROPERTY_GETTER(Document, nodeName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NewString(ctx, "#document"); -} - -IMPL_PROPERTY_GETTER(Document, all)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - auto all = new AllCollection(document->m_context); - - traverseNode(document, [&all](NodeInstance* node) { - all->internalAdd(node, nullptr); - return false; - }); - - return all->jsObject; -} - -// document.documentElement -IMPL_PROPERTY_GETTER(Document, documentElement)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - ElementInstance* documentElement = document->getDocumentElement(); - return documentElement == nullptr ? JS_NULL : documentElement->jsObject; -} - -// document.head -IMPL_PROPERTY_GETTER(Document, head)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - ElementInstance* documentElement = document->getDocumentElement(); - int32_t len = arrayGetLength(ctx, documentElement->childNodes); - JSValue head = JS_NULL; - if (documentElement != nullptr) { - for (int i = 0; i < len; i++) { - JSValue v = JS_GetPropertyUint32(ctx, documentElement->childNodes, i); - auto* nodeInstance = static_cast(JS_GetOpaque(v, Node::classId(v))); - if (nodeInstance->nodeType == NodeType::ELEMENT_NODE) { - auto* elementInstance = static_cast(nodeInstance); - if (elementInstance->tagName() == "HEAD") { - head = elementInstance->jsObject; - break; - } - } - JS_FreeValue(ctx, v); - } - - JS_FreeValue(ctx, documentElement->jsObject); - } - - return head; -} - -// document.body: https://html.spec.whatwg.org/multipage/dom.html#dom-document-body-dev -IMPL_PROPERTY_GETTER(Document, body)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - ElementInstance* documentElement = document->getDocumentElement(); - JSValue body = JS_NULL; - - if (documentElement != nullptr) { - int32_t len = arrayGetLength(ctx, documentElement->childNodes); - // The body element of a document is the first of the html documentElement's children that - // is either a body element or a frameset element, or null if there is no such element. - for (int i = 0; i < len; i++) { - JSValue v = JS_GetPropertyUint32(ctx, documentElement->childNodes, i); - auto* nodeInstance = static_cast(JS_GetOpaque(v, Node::classId(v))); - if (nodeInstance->nodeType == NodeType::ELEMENT_NODE) { - auto* elementInstance = static_cast(nodeInstance); - if (elementInstance->tagName() == "BODY") { - body = elementInstance->jsObject; - break; - } - } - JS_FreeValue(ctx, v); - } - JS_FreeValue(ctx, documentElement->jsObject); - } - return body; -} - -// The body property is settable, setting a new body on a document will effectively remove all -// the current children of the existing element. -IMPL_PROPERTY_SETTER(Document, body)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - ElementInstance* documentElement = document->getDocumentElement(); - // If there is no document element, throw a Exception. - if (documentElement == nullptr) { - return JS_ThrowInternalError(ctx, "No document element exists"); - } - JSValue result = JS_NULL; - JSValue newBody = argv[0]; - // If the body element is not null, then replace the body element with the new value within the body element's parent and return. - if (JS_IsInstanceOf(ctx, newBody, Element::instance(document->m_context)->jsObject)) { - auto* newElementInstance = static_cast(JS_GetOpaque(newBody, Element::classId())); - // If the new value is not a body element, then throw a Exception. - if (newElementInstance->tagName() == "BODY") { - JSValue oldBody = JS_GetPropertyStr(ctx, document->jsObject, "body"); - if (JS_VALUE_GET_PTR(oldBody) != JS_VALUE_GET_PTR(newBody)) { - // If the new value is the same as the body element. - if (JS_IsNull(oldBody)) { - // The old body element is null, but there's a document element. Append the new value to the document element. - documentElement->internalAppendChild(newElementInstance); - } else { - // Otherwise, replace the body element with the new value within the body element's parent. - auto* oldElementInstance = static_cast(JS_GetOpaque(oldBody, Element::classId())); - documentElement->internalReplaceChild(newElementInstance, oldElementInstance); - } - } - JS_FreeValue(ctx, oldBody); - result = JS_DupValue(ctx, newBody); - } else { - result = JS_ThrowTypeError(ctx, "The new body element must be a 'BODY' element"); - } - } else { - result = JS_ThrowTypeError(ctx, "The 1st argument provided is either null, or an invalid HTMLElement"); - } - - JS_FreeValue(ctx, documentElement->jsObject); - return result; -} - -// document.children -IMPL_PROPERTY_GETTER(Document, children)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - JSValue array = JS_NewArray(ctx); - JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); - - int32_t len = arrayGetLength(ctx, document->childNodes); - for (int i = 0; i < len; i++) { - JSValue v = JS_GetPropertyUint32(ctx, document->childNodes, i); - auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); - if (instance->nodeType == NodeType::ELEMENT_NODE) { - JSValue arguments[] = {v}; - JS_Call(ctx, pushMethod, array, 1, arguments); - } - JS_FreeValue(ctx, v); - } - - JS_FreeValue(ctx, pushMethod); - return array; -} - -IMPL_PROPERTY_GETTER(Document, cookie)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - std::string cookie = document->m_cookie->getCookie(); - return JS_NewString(ctx, cookie.c_str()); -} -IMPL_PROPERTY_SETTER(Document, cookie)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - std::string value = jsValueToStdString(ctx, argv[0]); - document->m_cookie->setCookie(value); - return JS_NULL; -} - -std::string DocumentCookie::getCookie() { - std::string result; - size_t i = 0; - for (auto& pair : cookiePairs) { - result += pair.first + "=" + pair.second; - i++; - if (i < cookiePairs.size()) { - result += "; "; - } - } - - return std::move(result); -} - -inline std::string trim(std::string& str) { - str.erase(0, str.find_first_not_of(' ')); // prefixing spaces - str.erase(str.find_last_not_of(' ') + 1); // surfixing spaces - return str; -} - -void DocumentCookie::setCookie(std::string& cookieStr) { - trim(cookieStr); - - std::string key; - std::string value; - - const std::regex cookie_regex("^[^=]*=([^;]*)"); - - if (!cookieStr.find('=', 0)) { - key = ""; - value = cookieStr; - } else { - size_t idx = cookieStr.find('=', 0); - key = cookieStr.substr(0, idx); - - std::match_results match_results; - // Only allow to set a single cookie at a time - // Find first cookie value if multiple cookie set - if (std::regex_match(cookieStr, match_results, cookie_regex)) { - if (match_results.size() == 2) { - value = match_results[1]; - - if (key.empty() && value.empty()) { - return; - } - } - } - } - - cookiePairs[key] = value; -} - -DocumentInstance::DocumentInstance(Document* document) : NodeInstance(document, NodeType::DOCUMENT_NODE, Document::classId(), "document") { - m_context->m_document = this; - m_document = this; - m_cookie = std::make_unique(); - m_eventTargetId = DOCUMENT_TARGET_ID; - - m_scriptAnimationController = makeGarbageCollected()->initialize(m_ctx, &ScriptAnimationController::classId); - -#if FLUTTER_BACKEND - getDartMethod()->initDocument(m_context->getContextId(), nativeEventTarget); -#endif -} - -DocumentInstance::~DocumentInstance() { - // Atom string should keep alive in memory to make sure same string have the corresponding id. - // Only freed after document finalized. - for (auto& entry : m_elementMapById) { - JS_FreeAtomRT(m_context->runtime(), entry.first); - // Note: someone may be curious why there are no JS_FreeValueRT() call in this finalize callbacks. - // m_elementMapById's value are all elements, which are JavaScript objects. Will be freed by GC at marking phase. - } -} -void DocumentInstance::removeElementById(JSAtom id, ElementInstance* element) { - if (m_elementMapById.count(id) > 0) { - auto& list = m_elementMapById[id]; - auto idx = std::find(list.begin(), list.end(), element); - assert_m(idx != list.end(), "Element should exist in idMap"); - list.erase(idx); - JS_FreeValue(m_ctx, element->jsObject); - } -} -void DocumentInstance::addElementById(JSAtom id, ElementInstance* element) { - if (m_elementMapById.count(id) == 0) { - m_elementMapById[id] = std::vector(); - JS_DupAtom(m_ctx, id); - } - - auto& list = m_elementMapById[id]; - auto it = std::find(list.begin(), list.end(), element); - - if (it == list.end()) { - JS_DupValue(m_ctx, element->jsObject); - m_elementMapById[id].emplace_back(element); - } -} - -ElementInstance* DocumentInstance::getDocumentElement() { - int32_t len = arrayGetLength(m_ctx, childNodes); - - for (int i = 0; i < len; i++) { - JSValue v = JS_GetPropertyUint32(m_ctx, childNodes, i); - auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); - if (instance->nodeType == NodeType::ELEMENT_NODE) { - return static_cast(instance); - } - JS_FreeValue(m_ctx, v); - } - - return nullptr; -} - -int32_t DocumentInstance::requestAnimationFrame(FrameCallback* frameCallback) { - return m_scriptAnimationController->registerFrameCallback(frameCallback); -} - -void DocumentInstance::cancelAnimationFrame(uint32_t callbackId) { - m_scriptAnimationController->cancelFrameCallback(callbackId); -} - -void DocumentInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - NodeInstance::trace(rt, val, mark_func); - // Trace scriptAnimationController - if (m_scriptAnimationController != nullptr) { - JS_MarkValue(rt, m_scriptAnimationController->toQuickJS(), mark_func); - } - // Trace elementByIdMaps - for (auto& entry : m_elementMapById) { - for (auto& value : entry.second) { - JS_MarkValue(rt, value->jsObject, mark_func); - } - } -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/document.h b/bridge/bindings/qjs/dom/document.h deleted file mode 100644 index 3555adecd0..0000000000 --- a/bridge/bindings/qjs/dom/document.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_DOCUMENT_H -#define KRAKENBRIDGE_DOCUMENT_H - -#include "element.h" -#include "frame_request_callback_collection.h" -#include "node.h" -#include "script_animation_controller.h" - -namespace kraken::binding::qjs { - -void bindDocument(ExecutionContext* context); - -using TraverseHandler = std::function; - -void traverseNode(NodeInstance* node, TraverseHandler handler); - -class Document : public Node { - public: - static JSClassID kDocumentClassID; - - Document() = delete; - Document(ExecutionContext* context); - - static JSClassID classId(); - - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - OBJECT_INSTANCE(Document); - - static JSValue createEvent(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue createElement(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue createTextNode(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue createDocumentFragment(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue createComment(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getElementById(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getElementsByTagName(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getElementsByClassName(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - JSValue getElementConstructor(ExecutionContext* context, const std::string& tagName); - bool isCustomElement(const std::string& tagName); - - private: - DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); - DEFINE_PROTOTYPE_READONLY_PROPERTY(all); - DEFINE_PROTOTYPE_READONLY_PROPERTY(location); - DEFINE_PROTOTYPE_READONLY_PROPERTY(documentElement); - DEFINE_PROTOTYPE_READONLY_PROPERTY(children); - DEFINE_PROTOTYPE_READONLY_PROPERTY(head); - - DEFINE_PROTOTYPE_PROPERTY(cookie); - DEFINE_PROTOTYPE_PROPERTY(body); - - DEFINE_PROTOTYPE_FUNCTION(createEvent, 1); - DEFINE_PROTOTYPE_FUNCTION(createElement, 1); - DEFINE_PROTOTYPE_FUNCTION(createDocumentFragment, 0); - DEFINE_PROTOTYPE_FUNCTION(createTextNode, 1); - DEFINE_PROTOTYPE_FUNCTION(createComment, 1); - DEFINE_PROTOTYPE_FUNCTION(getElementById, 1); - DEFINE_PROTOTYPE_FUNCTION(getElementsByTagName, 1); - DEFINE_PROTOTYPE_FUNCTION(getElementsByClassName, 1); - - void defineElement(const std::string& tagName, Element* constructor); - - friend DocumentInstance; - - bool event_registered{false}; - bool document_registered{false}; - std::unordered_map elementConstructorMap; -}; - -class DocumentCookie { - public: - DocumentCookie() = default; - - std::string getCookie(); - void setCookie(std::string& str); - - private: - std::unordered_map cookiePairs; -}; - -class DocumentInstance : public NodeInstance { - public: - DocumentInstance() = delete; - explicit DocumentInstance(Document* document); - ~DocumentInstance(); - - int32_t requestAnimationFrame(FrameCallback* frameCallback); - void cancelAnimationFrame(uint32_t callbackId); - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; - ElementInstance* getDocumentElement(); - - private: - void removeElementById(JSAtom id, ElementInstance* element); - void addElementById(JSAtom id, ElementInstance* element); - std::unordered_map> m_elementMapById; - ElementInstance* m_documentElement{nullptr}; - std::unique_ptr m_cookie; - - ScriptAnimationController* m_scriptAnimationController; - - friend Document; - friend ElementInstance; - friend ExecutionContext; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_DOCUMENT_H diff --git a/bridge/bindings/qjs/dom/document_fragment.cc b/bridge/bindings/qjs/dom/document_fragment.cc deleted file mode 100644 index 6979a12b7d..0000000000 --- a/bridge/bindings/qjs/dom/document_fragment.cc +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "document_fragment.h" -#include "document.h" -#include "kraken_bridge.h" - -namespace kraken::binding::qjs { - -void bindDocumentFragment(ExecutionContext* context) { - auto* constructor = DocumentFragment::instance(context); - context->defineGlobalProperty("DocumentFragment", constructor->jsObject); -} - -std::once_flag kDocumentFragmentFlag; - -JSClassID DocumentFragment::kDocumentFragmentID{0}; - -DocumentFragment::DocumentFragment(ExecutionContext* context) : Node(context) { - std::call_once(kDocumentFragmentFlag, []() { JS_NewClassID(&kDocumentFragmentID); }); - JS_SetPrototype(m_ctx, m_prototypeObject, Node::instance(m_context)->prototype()); -} - -JSClassID DocumentFragment::classId() { - return kDocumentFragmentID; -} - -JSValue DocumentFragment::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - return (new DocumentFragmentInstance(this))->jsObject; -} - -DocumentFragmentInstance::DocumentFragmentInstance(DocumentFragment* fragment) : NodeInstance(fragment, NodeType::DOCUMENT_FRAGMENT_NODE, DocumentFragment::classId(), "DocumentFragment") { - setNodeFlag(DocumentFragmentInstance::NodeFlag::IsDocumentFragment); - m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createDocumentFragment, nativeEventTarget); -} -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/document_fragment.h b/bridge/bindings/qjs/dom/document_fragment.h deleted file mode 100644 index 5b0e3c40a7..0000000000 --- a/bridge/bindings/qjs/dom/document_fragment.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_DOCUMENT_FRAGMENT_H -#define KRAKENBRIDGE_DOCUMENT_FRAGMENT_H - -#include "node.h" - -namespace kraken::binding::qjs { - -void bindDocumentFragment(ExecutionContext* context); - -class DocumentFragment : public Node { - public: - static JSClassID kDocumentFragmentID; - static JSClassID classId(); - - DocumentFragment() = delete; - explicit DocumentFragment(ExecutionContext* context); - - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - OBJECT_INSTANCE(DocumentFragment); -}; - -class DocumentFragmentInstance : public NodeInstance { - public: - DocumentFragmentInstance() = delete; - DocumentFragmentInstance(DocumentFragment* fragment); -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_DOCUMENT_FRAGMENT_H diff --git a/bridge/bindings/qjs/dom/document_test.cc b/bridge/bindings/qjs/dom/document_test.cc deleted file mode 100644 index a6bc30f0ac..0000000000 --- a/bridge/bindings/qjs/dom/document_test.cc +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "event_target.h" -#include "gtest/gtest.h" -#include "kraken_test_env.h" -#include "page.h" - -TEST(Document, createTextNode) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "
"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = - "let div = document.createElement('div');" - "div.setAttribute('hello', 1234);" - "document.body.appendChild(div);" - "let text = document.createTextNode('1234');" - "div.appendChild(text);" - "console.log(div);"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(Document, instanceofNode) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "true true true"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = "console.log(document instanceof Node, document instanceof Document, document instanceof EventTarget)"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(Document, createElementShouldWorkWithMultipleContext) { - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; - - kraken::KrakenPage* bridge1; - - const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; - - { - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); - auto context = bridge->getContext(); - bridge->evaluateScript(code, strlen(code), "vm://", 0); - bridge1 = bridge.release(); - } - - { - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); - auto context = bridge->getContext(); - const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - } - - bridge1->evaluateScript(code, strlen(code), "vm://", 0); - - delete bridge1; -} diff --git a/bridge/bindings/qjs/dom/element.cc b/bridge/bindings/qjs/dom/element.cc deleted file mode 100644 index 662739f1e5..0000000000 --- a/bridge/bindings/qjs/dom/element.cc +++ /dev/null @@ -1,900 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "element.h" -#include "bindings/qjs/bom/blob.h" -#include "bindings/qjs/html_parser.h" -#include "dart_methods.h" -#include "document.h" -#include "elements/template_element.h" -#include "text_node.h" - -#if UNIT_TEST -#include "kraken_test_env.h" -#endif - -namespace kraken::binding::qjs { - -std::once_flag kElementInitOnceFlag; - -void bindElement(ExecutionContext* context) { - auto* constructor = Element::instance(context); - // auto* domRectConstructor = BoundingClientRect - context->defineGlobalProperty("Element", constructor->jsObject); - context->defineGlobalProperty("HTMLElement", JS_DupValue(context->ctx(), constructor->jsObject)); -} - -bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue instance) { - if (JS_IsInstanceOf(context->ctx(), instance, Element::instance(context)->jsObject)) { - auto* elementInstance = static_cast(JS_GetOpaque(instance, Element::classId())); - std::string tagName = elementInstance->getRegisteredTagName(); - - // Special case for kraken official plugins. - if (tagName == "video" || tagName == "iframe") - return true; - - for (char i : tagName) { - if (i == '-') - return true; - } - } - - return false; -} - -JSClassID Element::kElementClassId{0}; - -Element::Element(ExecutionContext* context) : Node(context, "Element") { - std::call_once(kElementInitOnceFlag, []() { JS_NewClassID(&kElementClassId); }); - JS_SetPrototype(m_ctx, m_prototypeObject, Node::instance(m_context)->prototype()); -} - -JSClassID Element::classId() { - return kElementClassId; -} - -JSClassID ElementAttributes::classId{0}; -JSValue ElementAttributes::getAttribute(const std::string& name) { - bool numberIndex = isNumberIndex(name); - - if (numberIndex) { - return JS_NULL; - } - - return JS_DupValue(m_ctx, m_attributes[name]); -} - -JSValue ElementAttributes::setAttribute(const std::string& name, JSValue value) { - bool numberIndex = isNumberIndex(name); - - if (numberIndex) { - return JS_ThrowTypeError(m_ctx, "Failed to execute 'setAttribute' on 'Element': '%s' is not a valid attribute name.", name.c_str()); - } - - if (name == "class") { - std::string classNameString = jsValueToStdString(m_ctx, value); - m_className->set(classNameString); - } - - // If attribute exists, should free the previous value. - if (m_attributes.count(name) > 0) { - JS_FreeValue(m_ctx, m_attributes[name]); - } - - m_attributes[name] = JS_DupValue(m_ctx, value); - - return JS_NULL; -} - -bool ElementAttributes::hasAttribute(std::string& name) { - bool numberIndex = isNumberIndex(name); - - if (numberIndex) { - return false; - } - - return m_attributes.count(name) > 0; -} - -void ElementAttributes::removeAttribute(std::string& name) { - JSValue value = m_attributes[name]; - JS_FreeValue(m_ctx, value); - m_attributes.erase(name); -} - -void ElementAttributes::copyWith(ElementAttributes* attributes) { - for (auto& attr : attributes->m_attributes) { - m_attributes[attr.first] = JS_DupValue(m_ctx, attr.second); - } -} - -std::shared_ptr ElementAttributes::className() { - return m_className; -} - -std::string ElementAttributes::toString() { - std::string s; - - for (auto& attr : m_attributes) { - s += attr.first + "="; - const char* pstr = JS_ToCString(m_ctx, attr.second); - s += "\"" + std::string(pstr) + "\""; - JS_FreeCString(m_ctx, pstr); - } - - return s; -} - -void ElementAttributes::dispose() const { - for (auto& attr : m_attributes) { - JS_FreeValueRT(m_runtime, attr.second); - } -} -void ElementAttributes::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - for (auto& attr : m_attributes) { - JS_MarkValue(rt, attr.second, mark_func); - } -} - -JSValue Element::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - if (argc == 0) - return JS_ThrowTypeError(ctx, "Illegal constructor"); - JSValue tagName = argv[0]; - - if (!JS_IsString(tagName)) { - return JS_ThrowTypeError(ctx, "Illegal constructor"); - } - - auto* context = static_cast(JS_GetContextOpaque(ctx)); - std::string name = jsValueToStdString(ctx, tagName); - - auto* Document = Document::instance(context); - if (Document->isCustomElement(name)) { - return JS_CallConstructor(ctx, Document->getElementConstructor(context, name), argc, argv); - } - - auto* element = new ElementInstance(this, name, true); - return element->jsObject; -} - -JSValue Element::getBoundingClientRect(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); - getDartMethod()->flushUICommand(); - return element->invokeBindingMethod("getBoundingClientRect", 0, nullptr); -} - -JSValue Element::hasAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'hasAttribute' on 'Element': 1 argument required, but only 0 present"); - } - - JSValue nameValue = argv[0]; - - if (!JS_IsString(nameValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setAttribute' on 'Element': name attribute is not valid."); - } - - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - auto* attributes = element->m_attributes; - - const char* cname = JS_ToCString(ctx, nameValue); - std::string name = std::string(cname); - - JSValue result = JS_NewBool(ctx, attributes->hasAttribute(name)); - JS_FreeCString(ctx, cname); - - return result; -} - -JSValue Element::setAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc != 2) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setAttribute' on 'Element': 2 arguments required, but only %d present", argc); - } - - JSValue nameValue = argv[0]; - JSValue attributeValue = JS_ToString(ctx, argv[1]); - - if (!JS_IsString(nameValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setAttribute' on 'Element': name attribute is not valid."); - } - - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string name = jsValueToStdString(ctx, nameValue); - std::transform(name.begin(), name.end(), name.begin(), ::tolower); - - auto* attributes = element->m_attributes; - - if (attributes->hasAttribute(name)) { - JSValue oldAttribute = attributes->getAttribute(name); - JSValue exception = attributes->setAttribute(name, attributeValue); - if (JS_IsException(exception)) - return exception; - element->_didModifyAttribute(name, oldAttribute, attributeValue); - JS_FreeValue(ctx, oldAttribute); - } else { - JSValue exception = attributes->setAttribute(name, attributeValue); - if (JS_IsException(exception)) - return exception; - element->_didModifyAttribute(name, JS_NULL, attributeValue); - } - - std::unique_ptr args_01 = stringToNativeString(name); - std::unique_ptr args_02 = jsValueToNativeString(ctx, attributeValue); - - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setAttribute, *args_01, *args_02, nullptr); - - JS_FreeValue(ctx, attributeValue); - - return JS_NULL; -} - -JSValue Element::getAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc != 1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'getAttribute' on 'Element': 1 argument required, but only 0 present"); - } - - JSValue nameValue = argv[0]; - - if (!JS_IsString(nameValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setAttribute' on 'Element': name attribute is not valid."); - } - - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string name = jsValueToStdString(ctx, nameValue); - - auto* attributes = element->m_attributes; - - if (attributes->hasAttribute(name)) { - return attributes->getAttribute(name); - } - - return JS_NULL; -} - -JSValue Element::removeAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc != 1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'removeAttribute' on 'Element': 1 argument required, but only 0 present"); - } - - JSValue nameValue = argv[0]; - - if (!JS_IsString(nameValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'removeAttribute' on 'Element': name attribute is not valid."); - } - - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string name = jsValueToStdString(ctx, nameValue); - auto* attributes = element->m_attributes; - - if (attributes->hasAttribute(name)) { - JSValue targetValue = attributes->getAttribute(name); - element->m_attributes->removeAttribute(name); - element->_didModifyAttribute(name, targetValue, JS_NULL); - JS_FreeValue(ctx, targetValue); - - std::unique_ptr args_01 = stringToNativeString(name); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::removeAttribute, *args_01, nullptr); - } - - return JS_NULL; -} - -JSValue Element::toBlob(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - double devicePixelRatio = 1.0; - - if (argc > 0) { - JSValue devicePixelRatioValue = argv[0]; - - if (!JS_IsNumber(devicePixelRatioValue)) { - return JS_ThrowTypeError(ctx, "Failed to export blob: parameter 1 (devicePixelRatio) is not an number."); - } - - JS_ToFloat64(ctx, &devicePixelRatio, devicePixelRatioValue); - } - - if (getDartMethod()->toBlob == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to export blob: dart method (toBlob) is not registered."); - } - - auto* element = reinterpret_cast(JS_GetOpaque(this_val, Element::classId())); - getDartMethod()->flushUICommand(); - - auto blobCallback = [](void* callbackContext, int32_t contextId, const char* error, uint8_t* bytes, int32_t length) { - if (!isContextValid(contextId)) - return; - - auto promiseContext = static_cast(callbackContext); - JSContext* ctx = promiseContext->context->ctx(); - if (error == nullptr) { - std::vector vec(bytes, bytes + length); - JSValue arrayBuffer = JS_NewArrayBuffer(ctx, bytes, length, nullptr, nullptr, false); - Blob* constructor = Blob::instance(promiseContext->context); - JSValue argumentsArray = JS_NewArray(ctx); - JSValue pushMethod = JS_GetPropertyStr(ctx, argumentsArray, "push"); - JS_Call(ctx, pushMethod, argumentsArray, 1, &arrayBuffer); - JSValue blobValue = JS_CallConstructor(ctx, constructor->jsObject, 1, &argumentsArray); - - if (JS_IsException(blobValue)) { - promiseContext->context->handleException(&blobValue); - } else { - JSValue ret = JS_Call(ctx, promiseContext->resolveFunc, promiseContext->promise, 1, &blobValue); - promiseContext->context->handleException(&ret); - promiseContext->context->drainPendingPromiseJobs(); - JS_FreeValue(ctx, ret); - } - - JS_FreeValue(ctx, pushMethod); - JS_FreeValue(ctx, blobValue); - JS_FreeValue(ctx, argumentsArray); - JS_FreeValue(ctx, arrayBuffer); - } else { - JS_ThrowInternalError(ctx, "%s", error); - JSValue errorObject = JS_GetException(ctx); - JSValue ret = JS_Call(ctx, promiseContext->rejectFunc, promiseContext->promise, 1, &errorObject); - promiseContext->context->handleException(&ret); - promiseContext->context->drainPendingPromiseJobs(); - JS_FreeValue(ctx, errorObject); - JS_FreeValue(ctx, ret); - } - - promiseContext->context->drainPendingPromiseJobs(); - - JS_FreeValue(ctx, promiseContext->resolveFunc); - JS_FreeValue(ctx, promiseContext->rejectFunc); - list_del(&promiseContext->link); - delete promiseContext; - }; - - JSValue resolving_funcs[2]; - JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); - - auto toBlobPromiseContext = new PromiseContext{ - nullptr, element->m_context, resolving_funcs[0], resolving_funcs[1], promise, - }; - - getDartMethod()->toBlob(static_cast(toBlobPromiseContext), element->m_context->getContextId(), blobCallback, element->m_eventTargetId, devicePixelRatio); - list_add_tail(&toBlobPromiseContext->link, &element->m_context->promise_job_list); - - return promise; -} - -JSValue Element::click(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -#if FLUTTER_BACKEND - getDartMethod()->flushUICommand(); - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->invokeBindingMethod("click", 0, nullptr); -#elif UNIT_TEST - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); - TEST_dispatchEvent(element->m_contextId, element, "click"); - return JS_UNDEFINED; -#else - return JS_UNDEFINED; -#endif -} - -JSValue Element::scroll(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); - double arg0 = 0; - double arg1 = 0; - JS_ToFloat64(ctx, &arg0, argv[0]); - JS_ToFloat64(ctx, &arg1, argv[1]); - NativeValue arguments[] = {Native_NewFloat64(arg0), Native_NewFloat64(arg1)}; - return element->invokeBindingMethod("scroll", 2, arguments); -} - -JSValue Element::scrollBy(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); - double arg0 = 0; - double arg1 = 0; - JS_ToFloat64(ctx, &arg0, argv[0]); - JS_ToFloat64(ctx, &arg1, argv[1]); - NativeValue arguments[] = {Native_NewFloat64(arg0), Native_NewFloat64(arg1)}; - return element->invokeBindingMethod("scrollBy", 2, arguments); -} - -IMPL_PROPERTY_GETTER(Element, nodeName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string tagName = element->tagName(); - return JS_NewString(ctx, tagName.c_str()); -} - -IMPL_PROPERTY_GETTER(Element, tagName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string tagName = element->tagName(); - return JS_NewString(ctx, tagName.c_str()); -} - -IMPL_PROPERTY_GETTER(Element, className)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("className"); -} -IMPL_PROPERTY_SETTER(Element, className)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - JSValue value = argv[0]; - - // @TODO: Remove this line. - element->m_attributes->setAttribute("class", value); - - const char* string = JS_ToCString(ctx, value); - NativeValue nativeValue = Native_NewCString(string); - element->setBindingProperty("className", nativeValue); - JS_FreeCString(ctx, string); - return JS_DupValue(ctx, value); -} - -IMPL_PROPERTY_GETTER(Element, offsetLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("offsetLeft"); -} - -IMPL_PROPERTY_GETTER(Element, offsetTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("offsetTop"); -} - -IMPL_PROPERTY_GETTER(Element, offsetWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("offsetWidth"); -} - -IMPL_PROPERTY_GETTER(Element, offsetHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("offsetHeight"); -} - -IMPL_PROPERTY_GETTER(Element, clientWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("clientWidth"); -} - -IMPL_PROPERTY_GETTER(Element, clientHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("clientHeight"); -} - -IMPL_PROPERTY_GETTER(Element, clientTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("clientTop"); -} - -IMPL_PROPERTY_GETTER(Element, clientLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("clientLeft"); -} - -IMPL_PROPERTY_GETTER(Element, scrollTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("scrollTop"); -} -IMPL_PROPERTY_SETTER(Element, scrollTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - double floatValue = 0; - JSValue value = argv[0]; - JS_ToFloat64(ctx, &floatValue, value); - NativeValue nativeValue = Native_NewFloat64(floatValue); - element->setBindingProperty("scrollTop", nativeValue); - return JS_DupValue(ctx, value); -} - -IMPL_PROPERTY_GETTER(Element, scrollLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("scrollLeft"); -} -IMPL_PROPERTY_SETTER(Element, scrollLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - double floatValue = 0; - JSValue value = argv[0]; - JS_ToFloat64(ctx, &floatValue, value); - NativeValue nativeValue = Native_NewFloat64(floatValue); - element->setBindingProperty("scrollLeft", nativeValue); - return JS_DupValue(ctx, value); -} - -IMPL_PROPERTY_GETTER(Element, scrollHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("scrollHeight"); -} - -IMPL_PROPERTY_GETTER(Element, scrollWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("scrollWidth"); -} - -// Definition for firstElementChild -IMPL_PROPERTY_GETTER(Element, firstElementChild)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - int32_t len = arrayGetLength(ctx, element->childNodes); - - for (int i = 0; i < len; i++) { - JSValue v = JS_GetPropertyUint32(ctx, element->childNodes, i); - auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); - if (instance->nodeType == NodeType::ELEMENT_NODE) { - return instance->jsObject; - } - JS_FreeValue(ctx, v); - } - - return JS_NULL; -} - -// Definition for lastElementChild -IMPL_PROPERTY_GETTER(Element, lastElementChild)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - int32_t len = arrayGetLength(ctx, element->childNodes); - - for (int i = len - 1; i >= 0; i--) { - JSValue v = JS_GetPropertyUint32(ctx, element->childNodes, i); - auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); - if (instance->nodeType == NodeType::ELEMENT_NODE) { - return instance->jsObject; - } - JS_FreeValue(ctx, v); - } - - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(Element, children)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - JSValue array = JS_NewArray(ctx); - JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); - - int32_t len = arrayGetLength(ctx, element->childNodes); - - for (int i = 0; i < len; i++) { - JSValue v = JS_GetPropertyUint32(ctx, element->childNodes, i); - auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); - if (instance->nodeType == NodeType::ELEMENT_NODE) { - JSValue arguments[] = {v}; - JS_Call(ctx, pushMethod, array, 1, arguments); - } - JS_FreeValue(ctx, v); - } - - JS_FreeValue(ctx, pushMethod); - - return array; -} - -IMPL_PROPERTY_GETTER(Element, attributes)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return JS_DupValue(ctx, element->m_attributes->toQuickJS()); -} - -IMPL_PROPERTY_GETTER(Element, innerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return JS_NewString(ctx, element->innerHTML().c_str()); -} -IMPL_PROPERTY_SETTER(Element, innerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - const char* chtml = JS_ToCString(ctx, argv[0]); - - if (element->hasNodeFlag(NodeInstance::NodeFlag::IsTemplateElement)) { - auto* templateElement = static_cast(element); - HTMLParser::parseHTMLFragment(chtml, strlen(chtml), templateElement->content()); - } else { - HTMLParser::parseHTMLFragment(chtml, strlen(chtml), element); - } - - JS_FreeCString(ctx, chtml); - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(Element, outerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return JS_NewString(ctx, element->outerHTML().c_str()); -} -IMPL_PROPERTY_SETTER(Element, outerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} - -JSClassID ElementInstance::classID() { - return Element::classId(); -} - -ElementInstance::~ElementInstance() {} - -JSValue ElementInstance::internalGetTextContent() { - JSValue array = JS_NewArray(m_ctx); - JSValue pushMethod = JS_GetPropertyStr(m_ctx, array, "push"); - - int32_t len = arrayGetLength(m_ctx, childNodes); - - for (int i = 0; i < len; i++) { - JSValue n = JS_GetPropertyUint32(m_ctx, childNodes, i); - auto* node = static_cast(JS_GetOpaque(n, Node::classId(n))); - JSValue nodeText = node->internalGetTextContent(); - JS_Call(m_ctx, pushMethod, array, 1, &nodeText); - JS_FreeValue(m_ctx, nodeText); - JS_FreeValue(m_ctx, n); - } - - JSValue joinMethod = JS_GetPropertyStr(m_ctx, array, "join"); - JSValue emptyString = JS_NewString(m_ctx, ""); - JSValue joinArgs[] = {emptyString}; - JSValue returnValue = JS_Call(m_ctx, joinMethod, array, 1, joinArgs); - - JS_FreeValue(m_ctx, array); - JS_FreeValue(m_ctx, pushMethod); - JS_FreeValue(m_ctx, joinMethod); - JS_FreeValue(m_ctx, emptyString); - return returnValue; -} - -void ElementInstance::internalSetTextContent(JSValue content) { - internalClearChild(); - - JSValue textNodeValue = JS_CallConstructor(m_ctx, TextNode::instance(m_context)->jsObject, 1, &content); - auto* textNodeInstance = static_cast(JS_GetOpaque(textNodeValue, TextNode::classId())); - internalAppendChild(textNodeInstance); - JS_FreeValue(m_ctx, textNodeValue); -} - -std::shared_ptr ElementInstance::classNames() { - return m_attributes->className(); -} - -std::string SpaceSplitString::m_delimiter{" "}; - -void SpaceSplitString::set(std::string& string) { - size_t pos = 0; - std::string token; - std::string s = string; - while ((pos = s.find(m_delimiter)) != std::string::npos) { - token = s.substr(0, pos); - m_szData.push_back(token); - s.erase(0, pos + m_delimiter.length()); - } - m_szData.push_back(s); -} - -bool SpaceSplitString::contains(std::string& string) { - for (std::string& s : m_szData) { - if (s == string) { - return true; - } - } - return false; -} - -bool SpaceSplitString::containsAll(std::string s) { - std::vector szData; - size_t pos = 0; - std::string token; - - while ((pos = s.find(m_delimiter)) != std::string::npos) { - token = s.substr(0, pos); - szData.push_back(token); - s.erase(0, pos + m_delimiter.length()); - } - szData.push_back(s); - - bool flag = true; - for (std::string& str : szData) { - bool isContains = false; - for (std::string& data : m_szData) { - if (data == str) { - isContains = true; - break; - } - } - flag &= isContains; - } - - return flag; -} - -std::string ElementInstance::tagName() { - std::string tagName = std::string(m_tagName); - std::transform(tagName.begin(), tagName.end(), tagName.begin(), ::toupper); - return tagName; -} - -std::string ElementInstance::getRegisteredTagName() { - return m_tagName; -} - -std::string ElementInstance::outerHTML() { - std::string s = "<" + getRegisteredTagName(); - - // Read attributes - std::string attributes = m_attributes->toString(); - // Read style - std::string style = m_style->toString(); - - if (!attributes.empty()) { - s += " " + attributes; - } - if (!style.empty()) { - s += " style=\"" + style; - } - - s += ">"; - - std::string childHTML = innerHTML(); - s += childHTML; - s += ""; - - return s; -} - -std::string ElementInstance::innerHTML() { - std::string s; - - // If Element is TemplateElement, the innerHTML content is the content of documentFragment. - NodeInstance* parent = this; - if (hasNodeFlag(NodeInstance::NodeFlag::IsTemplateElement)) { - parent = static_cast(this)->content(); - } - - // Children toString - int32_t childLen = arrayGetLength(m_ctx, parent->childNodes); - - if (childLen == 0) - return s; - - for (int i = 0; i < childLen; i++) { - JSValue c = JS_GetPropertyUint32(m_ctx, parent->childNodes, i); - auto* node = static_cast(JS_GetOpaque(c, Node::classId(c))); - if (node->nodeType == NodeType::ELEMENT_NODE) { - s += reinterpret_cast(node)->outerHTML(); - } else if (node->nodeType == NodeType::TEXT_NODE) { - s += reinterpret_cast(node)->toString(); - } - - JS_FreeValue(m_ctx, c); - } - return s; -} - -void ElementInstance::_notifyNodeRemoved(NodeInstance* insertionNode) { - if (insertionNode->isConnected()) { - traverseNode(this, [](NodeInstance* node) { - auto* Element = Element::instance(node->m_context); - if (node->prototype() == Element) { - auto element = reinterpret_cast(node); - element->_notifyChildRemoved(); - } - - return false; - }); - } -} - -void ElementInstance::_notifyChildRemoved() { - std::string prop = "id"; - if (m_attributes->hasAttribute(prop)) { - JSValue idValue = m_attributes->getAttribute(prop); - JSAtom id = JS_ValueToAtom(m_ctx, idValue); - document()->removeElementById(id, this); - JS_FreeValue(m_ctx, idValue); - JS_FreeAtom(m_ctx, id); - } -} - -void ElementInstance::_notifyNodeInsert(NodeInstance* insertNode) { - if (insertNode->isConnected()) { - traverseNode(this, [](NodeInstance* node) { - auto* Element = Element::instance(node->m_context); - if (node->prototype() == Element) { - auto element = reinterpret_cast(node); - element->_notifyChildInsert(); - } - - return false; - }); - } -} - -void ElementInstance::_notifyChildInsert() { - std::string prop = "id"; - if (m_attributes->hasAttribute(prop)) { - JSValue idValue = m_attributes->getAttribute(prop); - JSAtom id = JS_ValueToAtom(m_ctx, idValue); - document()->addElementById(id, this); - JS_FreeValue(m_ctx, idValue); - JS_FreeAtom(m_ctx, id); - } -} - -void ElementInstance::_didModifyAttribute(std::string& name, JSValue oldId, JSValue newId) { - if (name == "id") { - _beforeUpdateId(oldId, newId); - } -} - -void ElementInstance::_beforeUpdateId(JSValue oldIdValue, JSValue newIdValue) { - JSAtom oldId = JS_ValueToAtom(m_ctx, oldIdValue); - JSAtom newId = JS_ValueToAtom(m_ctx, newIdValue); - - if (oldId == newId) { - JS_FreeAtom(m_ctx, oldId); - JS_FreeAtom(m_ctx, newId); - return; - } - - if (!JS_IsNull(oldIdValue)) { - document()->removeElementById(oldId, this); - } - - if (!JS_IsNull(newIdValue)) { - document()->addElementById(newId, this); - } - - JS_FreeAtom(m_ctx, oldId); - JS_FreeAtom(m_ctx, newId); -} - -void ElementInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - if (m_attributes != nullptr) { - JS_MarkValue(rt, m_attributes->toQuickJS(), mark_func); - } - NodeInstance::trace(rt, val, mark_func); -} - -ElementInstance::ElementInstance(Element* element, std::string tagName, bool shouldAddUICommand) - : m_tagName(tagName), NodeInstance(element, NodeType::ELEMENT_NODE, Element::classId(), exoticMethods, "Element") { - m_attributes = makeGarbageCollected()->initialize(m_ctx, &ElementAttributes::classId); - JSValue arguments[] = {jsObject}; - JSValue style = JS_CallConstructor(m_ctx, CSSStyleDeclaration::instance(m_context)->jsObject, 1, arguments); - m_style = static_cast(JS_GetOpaque(style, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - - JS_DefinePropertyValueStr(m_ctx, jsObject, "style", m_style->jsObject, JS_PROP_C_W_E); - - if (shouldAddUICommand) { - std::unique_ptr args_01 = stringToNativeString(tagName); - element->m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createElement, *args_01, nativeEventTarget); - } -} - -JSClassExoticMethods ElementInstance::exoticMethods{nullptr, nullptr, nullptr, nullptr, hasProperty, getProperty, setProperty}; - -StyleDeclarationInstance* ElementInstance::style() { - return m_style; -} - -IMPL_PROPERTY_GETTER(BoundingClientRect, x)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->x); -} - -IMPL_PROPERTY_GETTER(BoundingClientRect, y)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->y); -} - -IMPL_PROPERTY_GETTER(BoundingClientRect, width)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->width); -} - -IMPL_PROPERTY_GETTER(BoundingClientRect, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->height); -} - -IMPL_PROPERTY_GETTER(BoundingClientRect, top)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->top); -} - -IMPL_PROPERTY_GETTER(BoundingClientRect, right)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->right); -} - -IMPL_PROPERTY_GETTER(BoundingClientRect, bottom)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->bottom); -} - -IMPL_PROPERTY_GETTER(BoundingClientRect, left)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->left); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/bindings/qjs/dom/element.h deleted file mode 100644 index 0e3d64ab82..0000000000 --- a/bridge/bindings/qjs/dom/element.h +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_ELEMENT_H -#define KRAKENBRIDGE_ELEMENT_H - -#include -#include "bindings/qjs/garbage_collected.h" -#include "bindings/qjs/host_object.h" -#include "node.h" -#include "style_declaration.h" - -namespace kraken::binding::qjs { - -void bindElement(ExecutionContext* context); - -class ElementInstance; - -class Element; - -using ElementCreator = ElementInstance* (*)(Element* element, std::string tagName); - -struct NativeBoundingClientRect { - double x; - double y; - double width; - double height; - double top; - double right; - double bottom; - double left; -}; - -class SpaceSplitString { - public: - SpaceSplitString() = default; - explicit SpaceSplitString(std::string string) { set(string); } - - void set(std::string& string); - bool contains(std::string& string); - bool containsAll(std::string s); - - private: - static std::string m_delimiter; - std::vector m_szData; -}; - -// TODO: refactor for better W3C standard support and higher performance. -class ElementAttributes : public GarbageCollected { - public: - static JSClassID classId; - - FORCE_INLINE const char* getHumanReadableName() const override { return "ElementAttributes"; } - - void dispose() const override; - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; - - JSValue getAttribute(const std::string& name); - JSValue setAttribute(const std::string& name, JSValue value); - bool hasAttribute(std::string& name); - void removeAttribute(std::string& name); - void copyWith(ElementAttributes* attributes); - std::shared_ptr className(); - std::string toString(); - - private: - std::unordered_map m_attributes; - std::shared_ptr m_className{std::make_shared("")}; -}; - -bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue instance); - -class Element : public Node { - public: - static JSClassID kElementClassId; - Element() = delete; - explicit Element(ExecutionContext* context); - - static JSClassID classId(); - - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - static JSValue getBoundingClientRect(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue hasAttribute(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue setAttribute(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getAttribute(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue removeAttribute(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue toBlob(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue click(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue scroll(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue scrollBy(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - OBJECT_INSTANCE(Element); - - private: - DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); - DEFINE_PROTOTYPE_READONLY_PROPERTY(tagName); - DEFINE_PROTOTYPE_READONLY_PROPERTY(offsetLeft); - DEFINE_PROTOTYPE_READONLY_PROPERTY(offsetTop); - DEFINE_PROTOTYPE_READONLY_PROPERTY(offsetWidth); - DEFINE_PROTOTYPE_READONLY_PROPERTY(offsetHeight); - DEFINE_PROTOTYPE_READONLY_PROPERTY(clientWidth); - DEFINE_PROTOTYPE_READONLY_PROPERTY(clientHeight); - DEFINE_PROTOTYPE_READONLY_PROPERTY(clientTop); - DEFINE_PROTOTYPE_READONLY_PROPERTY(clientLeft); - DEFINE_PROTOTYPE_READONLY_PROPERTY(scrollHeight); - DEFINE_PROTOTYPE_READONLY_PROPERTY(scrollWidth); - DEFINE_PROTOTYPE_READONLY_PROPERTY(firstElementChild); - DEFINE_PROTOTYPE_READONLY_PROPERTY(lastElementChild); - DEFINE_PROTOTYPE_READONLY_PROPERTY(children); - DEFINE_PROTOTYPE_READONLY_PROPERTY(attributes); - - DEFINE_PROTOTYPE_PROPERTY(className); - DEFINE_PROTOTYPE_PROPERTY(innerHTML); - DEFINE_PROTOTYPE_PROPERTY(outerHTML); - DEFINE_PROTOTYPE_PROPERTY(scrollTop); - DEFINE_PROTOTYPE_PROPERTY(scrollLeft); - - DEFINE_PROTOTYPE_FUNCTION(getBoundingClientRect, 0); - DEFINE_PROTOTYPE_FUNCTION(hasAttribute, 1); - DEFINE_PROTOTYPE_FUNCTION(setAttribute, 2); - DEFINE_PROTOTYPE_FUNCTION(getAttribute, 2); - DEFINE_PROTOTYPE_FUNCTION(removeAttribute, 1); - DEFINE_PROTOTYPE_FUNCTION(toBlob, 0); - DEFINE_PROTOTYPE_FUNCTION(click, 2); - DEFINE_PROTOTYPE_FUNCTION(scroll, 2); - // ScrollTo is same as scroll which reuse scroll functions. Macro expand is not support here. - ObjectFunction m_scrollTo{m_context, m_prototypeObject, "scrollTo", scroll, 2}; - DEFINE_PROTOTYPE_FUNCTION(scrollBy, 2); - friend ElementInstance; -}; - -struct PersistElement { - ElementInstance* element; - list_head link; -}; - -class ElementInstance : public NodeInstance { - public: - ElementInstance() = delete; - ~ElementInstance(); - JSValue internalGetTextContent() override; - void internalSetTextContent(JSValue content) override; - - std::shared_ptr classNames(); - std::string tagName(); - std::string getRegisteredTagName(); - std::string outerHTML(); - std::string innerHTML(); - StyleDeclarationInstance* style(); - - static inline JSClassID classID(); - - protected: - explicit ElementInstance(Element* element, std::string tagName, bool shouldAddUICommand); - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; - - private: - void _notifyNodeRemoved(NodeInstance* node) override; - void _notifyChildRemoved(); - void _notifyNodeInsert(NodeInstance* insertNode) override; - void _notifyChildInsert(); - void _didModifyAttribute(std::string& name, JSValue oldId, JSValue newId); - void _beforeUpdateId(JSValue oldIdValue, JSValue newIdValue); - - std::string m_tagName; - friend Element; - friend NodeInstance; - friend Node; - friend DocumentInstance; - StyleDeclarationInstance* m_style{nullptr}; - ElementAttributes* m_attributes{nullptr}; - - static JSClassExoticMethods exoticMethods; -}; - -class BoundingClientRect : public HostObject { - public: - BoundingClientRect() = delete; - explicit BoundingClientRect(ExecutionContext* context, NativeBoundingClientRect* nativeBoundingClientRect) - : HostObject(context, "BoundingClientRect"), m_nativeBoundingClientRect(nativeBoundingClientRect){}; - - private: - DEFINE_READONLY_PROPERTY(x); - DEFINE_READONLY_PROPERTY(y); - DEFINE_READONLY_PROPERTY(width); - DEFINE_READONLY_PROPERTY(height); - DEFINE_READONLY_PROPERTY(top); - DEFINE_READONLY_PROPERTY(right); - DEFINE_READONLY_PROPERTY(bottom); - DEFINE_READONLY_PROPERTY(left); - - NativeBoundingClientRect* m_nativeBoundingClientRect{nullptr}; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_ELEMENT_H diff --git a/bridge/bindings/qjs/dom/elements/.gitignore b/bridge/bindings/qjs/dom/elements/.gitignore deleted file mode 100644 index 514978282a..0000000000 --- a/bridge/bindings/qjs/dom/elements/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.gen diff --git a/bridge/bindings/qjs/dom/elements/image_element.cc b/bridge/bindings/qjs/dom/elements/image_element.cc deleted file mode 100644 index 99f9aa9c4e..0000000000 --- a/bridge/bindings/qjs/dom/elements/image_element.cc +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "image_element.h" -#include "bindings/qjs/qjs_patch.h" -#include "page.h" - -namespace kraken::binding::qjs { - -ImageElement::ImageElement(ExecutionContext* context) : Element(context) { - JS_SetPrototype(m_ctx, m_prototypeObject, Element::instance(m_context)->prototype()); -} - -void bindImageElement(ExecutionContext* context) { - auto* constructor = ImageElement::instance(context); - context->defineGlobalProperty("HTMLImageElement", constructor->jsObject); - context->defineGlobalProperty("Image", JS_DupValue(context->ctx(), constructor->jsObject)); -} - -JSValue ImageElement::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - auto instance = new ImageElementInstance(this); - return instance->jsObject; -} -IMPL_PROPERTY_GETTER(ImageElement, width)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("width"); -} -IMPL_PROPERTY_SETTER(ImageElement, width)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string key = "width"; - std::unique_ptr args_01 = stringToNativeString(key); - std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setAttribute, *args_01, *args_02, nullptr); - return JS_NULL; -} -IMPL_PROPERTY_GETTER(ImageElement, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("height"); -} -IMPL_PROPERTY_SETTER(ImageElement, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string key = "height"; - std::unique_ptr args_01 = stringToNativeString(key); - std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setAttribute, *args_01, *args_02, nullptr); - return JS_NULL; -} -IMPL_PROPERTY_GETTER(ImageElement, naturalWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("naturalWidth"); -} -IMPL_PROPERTY_GETTER(ImageElement, naturalHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("naturalHeight"); -} -IMPL_PROPERTY_GETTER(ImageElement, src)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("src"); -} -IMPL_PROPERTY_SETTER(ImageElement, src)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string key = "src"; - std::unique_ptr args_01 = stringToNativeString(key); - std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setAttribute, *args_01, *args_02, nullptr); - return JS_NULL; -} -IMPL_PROPERTY_GETTER(ImageElement, loading)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("loading"); -} -IMPL_PROPERTY_SETTER(ImageElement, loading)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string key = "loading"; - std::unique_ptr args_01 = stringToNativeString(key); - std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setAttribute, *args_01, *args_02, nullptr); - return JS_NULL; -} -IMPL_PROPERTY_GETTER(ImageElement, scaling)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("scaling"); -} -IMPL_PROPERTY_SETTER(ImageElement, scaling)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string key = "scaling"; - std::unique_ptr args_01 = stringToNativeString(key); - std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setAttribute, *args_01, *args_02, nullptr); - return JS_NULL; -} - -ImageElementInstance::ImageElementInstance(ImageElement* element) : ElementInstance(element, "img", true) { - // Protect image instance util load or error event triggered. - refer(); -} - -bool ImageElementInstance::dispatchEvent(EventInstance* event) { - std::u16string u16EventType = std::u16string(reinterpret_cast(event->type()->string), event->type()->length); - std::string eventType = toUTF8(u16EventType); - bool result = EventTargetInstance::dispatchEvent(event); - - // Free image instance after load or error event triggered. - if ((eventType == "load" || eventType == "error") && !freed) { - freed = true; - unrefer(); - } - - return result; -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/elements/image_element.h b/bridge/bindings/qjs/dom/elements/image_element.h deleted file mode 100644 index 469e9383bb..0000000000 --- a/bridge/bindings/qjs/dom/elements/image_element.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_IMAGE_ELEMENT_H -#define KRAKENBRIDGE_IMAGE_ELEMENT_H - -#include "bindings/qjs/dom/element.h" - -namespace kraken::binding::qjs { - -void bindImageElement(ExecutionContext* context); - -class ImageElementInstance; -class ImageElement : public Element { - public: - ImageElement() = delete; - explicit ImageElement(ExecutionContext* context); - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - OBJECT_INSTANCE(ImageElement); - - private: - DEFINE_PROTOTYPE_READONLY_PROPERTY(naturalWidth); - DEFINE_PROTOTYPE_READONLY_PROPERTY(naturalHeight); - - DEFINE_PROTOTYPE_PROPERTY(width); - DEFINE_PROTOTYPE_PROPERTY(height); - DEFINE_PROTOTYPE_PROPERTY(src); - DEFINE_PROTOTYPE_PROPERTY(loading); - DEFINE_PROTOTYPE_PROPERTY(scaling); - friend ImageElementInstance; -}; - -class ImageElementInstance : public ElementInstance { - public: - ImageElementInstance() = delete; - explicit ImageElementInstance(ImageElement* element); - bool dispatchEvent(EventInstance* event); - - private: - bool freed{false}; - friend ImageElement; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_IMAGE_ELEMENTT_H diff --git a/bridge/bindings/qjs/dom/elements/object_element.d.ts b/bridge/bindings/qjs/dom/elements/object_element.d.ts deleted file mode 100644 index 6804b1430f..0000000000 --- a/bridge/bindings/qjs/dom/elements/object_element.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -interface HostObject {} -interface Element {} - -interface ObjectElement extends Element { - type: string; - data: string; -} diff --git a/bridge/bindings/qjs/dom/elements/script_element.d.ts b/bridge/bindings/qjs/dom/elements/script_element.d.ts deleted file mode 100644 index 8b0ac61a02..0000000000 --- a/bridge/bindings/qjs/dom/elements/script_element.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -interface HostObject {} -interface Element {} - -interface ScriptElement extends Element { - src: string; - async: boolean; - defer: boolean; - type: string; - charset: string; - text: string; -} diff --git a/bridge/bindings/qjs/dom/elements/template_element.cc b/bridge/bindings/qjs/dom/elements/template_element.cc deleted file mode 100644 index 6194249641..0000000000 --- a/bridge/bindings/qjs/dom/elements/template_element.cc +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "template_element.h" -#include "bindings/qjs/dom/text_node.h" -#include "bindings/qjs/qjs_patch.h" -#include "page.h" - -namespace kraken::binding::qjs { - -TemplateElement::TemplateElement(ExecutionContext* context) : Element(context) { - JS_SetPrototype(m_ctx, m_prototypeObject, Element::instance(m_context)->prototype()); -} - -void bindTemplateElement(ExecutionContext* context) { - auto* constructor = TemplateElement::instance(context); - context->defineGlobalProperty("HTMLTemplateElement", constructor->jsObject); -} - -JSValue TemplateElement::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - auto instance = new TemplateElementInstance(this); - return instance->jsObject; -} - -DocumentFragmentInstance* TemplateElementInstance::content() const { - return static_cast(JS_GetOpaque(m_content.value(), DocumentFragment::classId())); -} - -TemplateElementInstance::TemplateElementInstance(TemplateElement* element) : ElementInstance(element, "template", true) { - setNodeFlag(NodeFlag::IsTemplateElement); -} - -TemplateElementInstance::~TemplateElementInstance() {} - -void TemplateElementInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - ElementInstance::trace(rt, val, mark_func); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/elements/template_element.h b/bridge/bindings/qjs/dom/elements/template_element.h deleted file mode 100644 index c22558fa5d..0000000000 --- a/bridge/bindings/qjs/dom/elements/template_element.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_TEMPLATE_ELEMENT_H -#define KRAKENBRIDGE_TEMPLATE_ELEMENT_H - -#include "bindings/qjs/dom/document_fragment.h" -#include "bindings/qjs/dom/element.h" - -namespace kraken::binding::qjs { - -void bindTemplateElement(ExecutionContext* context); -class TemplateElementInstance; - -class TemplateElement : public Element { - public: - TemplateElement() = delete; - explicit TemplateElement(ExecutionContext* context); - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - OBJECT_INSTANCE(TemplateElement); - - private: - friend TemplateElementInstance; -}; - -class TemplateElementInstance : public ElementInstance { - public: - TemplateElementInstance() = delete; - explicit TemplateElementInstance(TemplateElement* element); - ~TemplateElementInstance(); - - DocumentFragmentInstance* content() const; - - protected: - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; - - private: - ObjectProperty m_content{m_context, jsObject, "content", JS_CallConstructor(m_ctx, DocumentFragment::instance(m_context)->jsObject, 0, nullptr)}; - friend TemplateElement; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_TEMPLATE_ELEMENTT_H diff --git a/bridge/bindings/qjs/dom/event.cc b/bridge/bindings/qjs/dom/event.cc deleted file mode 100644 index cc5d3b0805..0000000000 --- a/bridge/bindings/qjs/dom/event.cc +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "event.h" -#include "bindings/qjs/bom/window.h" -#include "bindings/qjs/qjs_patch.h" -#include "custom_event.h" -#include "event_target.h" -#include "kraken_bridge.h" - -namespace kraken::binding::qjs { - -std::once_flag kEventInitOnceFlag; - -void bindEvent(ExecutionContext* context) { - auto* constructor = Event::instance(context); - context->defineGlobalProperty("Event", constructor->jsObject); -} - -JSClassID Event::kEventClassID{0}; - -Event::Event(ExecutionContext* context) : HostClass(context, "Event") { - std::call_once(kEventInitOnceFlag, []() { JS_NewClassID(&kEventClassID); }); -} - -JSValue Event::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to construct 'Event': 1 argument required, but only 0 present."); - } - - JSValue eventTypeValue = argv[0]; - std::string eventType = jsValueToStdString(ctx, eventTypeValue); - -#if ANDROID_32_BIT - auto* nativeEvent = new NativeEvent{reinterpret_cast(stringToNativeString(eventType).release())}; -#else - auto* nativeEvent = new NativeEvent{stringToNativeString(eventType).release()}; -#endif - auto* event = Event::buildEventInstance(eventType, m_context, nativeEvent, false); - return event->jsObject; -} - -std::unordered_map Event::m_eventCreatorMap{}; - -IMPL_PROPERTY_GETTER(Event, type)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* pType = reinterpret_cast(eventInstance->nativeEvent->type); - return JS_NewUnicodeString(ExecutionContext::runtime(), eventInstance->context()->ctx(), pType->string, pType->length); -} - -IMPL_PROPERTY_GETTER(Event, bubbles)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - return JS_NewBool(ctx, eventInstance->nativeEvent->bubbles); -} - -IMPL_PROPERTY_GETTER(Event, cancelable)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - return JS_NewBool(ctx, eventInstance->nativeEvent->cancelable); -} - -IMPL_PROPERTY_GETTER(Event, timestamp)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - return JS_NewInt64(ctx, eventInstance->nativeEvent->timeStamp); -} - -IMPL_PROPERTY_GETTER(Event, defaultPrevented)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - return JS_NewBool(ctx, eventInstance->cancelled()); -} - -IMPL_PROPERTY_GETTER(Event, target)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - - if (eventInstance->target() != nullptr) { - return JS_DupValue(ctx, ensureWindowIsGlobal(eventInstance->target())); - } - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(Event, srcElement)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - - if (eventInstance->target() != nullptr) { - return JS_DupValue(ctx, ensureWindowIsGlobal(eventInstance->target())); - } - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(Event, currentTarget)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - - if (eventInstance->currentTarget() != nullptr) { - return JS_DupValue(ctx, ensureWindowIsGlobal(eventInstance->currentTarget())); - } - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(Event, returnValue)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - return JS_NewBool(ctx, !eventInstance->cancelled()); -} - -IMPL_PROPERTY_GETTER(Event, cancelBubble)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - return JS_NewBool(ctx, eventInstance->cancelled()); -} - -EventInstance* Event::buildEventInstance(std::string& eventType, ExecutionContext* context, void* nativeEvent, bool isCustomEvent) { - EventInstance* eventInstance; - if (isCustomEvent) { - eventInstance = new CustomEventInstance(CustomEvent::instance(context), reinterpret_cast(nativeEvent)); - } else if (m_eventCreatorMap.count(eventType) > 0) { - eventInstance = m_eventCreatorMap[eventType](context, nativeEvent); - } else { - eventInstance = EventInstance::fromNativeEvent(Event::instance(context), static_cast(nativeEvent)); - } - - return eventInstance; -} - -void Event::defineEvent(const std::string& eventType, EventCreator creator) { - m_eventCreatorMap[eventType] = creator; -} - -JSValue Event::stopPropagation(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - event->m_propagationStopped = true; - return JS_NULL; -} - -JSValue Event::stopImmediatePropagation(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - event->m_propagationStopped = true; - event->m_propagationImmediatelyStopped = true; - return JS_NULL; -} - -JSValue Event::preventDefault(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - if (event->nativeEvent->cancelable) { - event->m_cancelled = true; - } - return JS_NULL; -} - -JSValue Event::initEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to initEvent required, but only 0 present."); - } - - JSValue typeValue = argv[0]; - JSValue bubblesValue = JS_NULL; - JSValue cancelableValue = JS_NULL; - if (argc > 1) { - bubblesValue = argv[1]; - } - - if (argc > 2) { - cancelableValue = argv[2]; - } - - if (!JS_IsString(typeValue)) { - return JS_ThrowTypeError(ctx, "Failed to initEvent: type should be a string."); - } - - auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - event->setType(jsValueToNativeString(ctx, typeValue).release()); - - if (!JS_IsNull(bubblesValue)) { - event->nativeEvent->bubbles = JS_IsBool(bubblesValue) ? 1 : 0; - } - if (!JS_IsNull(cancelableValue)) { - event->nativeEvent->cancelable = JS_IsBool(cancelableValue) ? 1 : 0; - } - return JS_NULL; -} - -EventInstance* EventInstance::fromNativeEvent(Event* event, NativeEvent* nativeEvent) { - return new EventInstance(event, nativeEvent); -} - -void EventInstance::setType(NativeString* type) const { -#if ANDROID_32_BIT - nativeEvent->type = reinterpret_cast(type); -#else - nativeEvent->type = type; -#endif -} - -EventTargetInstance* EventInstance::target() const { - return reinterpret_cast(nativeEvent->target)->instance; -} - -void EventInstance::setTarget(EventTargetInstance* target) const { -#if ANDROID_32_BIT - nativeEvent->target = reinterpret_cast(target); -#else - nativeEvent->target = target->nativeEventTarget; -#endif -} - -EventTargetInstance* EventInstance::currentTarget() const { - return reinterpret_cast(nativeEvent->currentTarget)->instance; -} - -void EventInstance::setCurrentTarget(EventTargetInstance* currentTarget) const { -#if ANDROID_32_BIT - nativeEvent->currentTarget = reinterpret_cast(currentTarget); -#else - nativeEvent->currentTarget = currentTarget->nativeEventTarget; -#endif -} - -EventInstance::EventInstance(Event* event, NativeEvent* nativeEvent) : nativeEvent(nativeEvent), Instance(event, "Event", nullptr, Event::kEventClassID, finalizer) {} -EventInstance::EventInstance(Event* jsEvent, JSAtom eventType, JSValue eventInit) : Instance(jsEvent, "Event", nullptr, Event::kEventClassID, finalizer) { - JSValue v = JS_AtomToValue(m_ctx, eventType); -#if ANDROID_32_BIT - nativeEvent = new NativeEvent{reinterpret_cast(jsValueToNativeString(m_ctx, v).release())}; -#else - nativeEvent = new NativeEvent{jsValueToNativeString(m_ctx, v).release()}; -#endif - JS_FreeValue(m_ctx, v); - - auto ms = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); - nativeEvent->timeStamp = ms.count(); - - if (!JS_IsNull(eventInit)) { - ; - JSAtom bubblesKey = JS_NewAtom(m_ctx, "bubbles"); - if (JS_HasProperty(m_ctx, eventInit, bubblesKey)) { - nativeEvent->bubbles = JS_ToBool(m_ctx, JS_GetProperty(m_ctx, eventInit, bubblesKey)); - } - JS_FreeAtom(m_ctx, bubblesKey); - - JSAtom cancelableKey = JS_NewAtom(m_ctx, "cancelable"); - if (JS_HasProperty(m_ctx, eventInit, cancelableKey)) { - nativeEvent->cancelable = JS_ToBool(m_ctx, JS_GetProperty(m_ctx, eventInit, cancelableKey)); - } - JS_FreeAtom(m_ctx, cancelableKey); - } -} - -void EventInstance::finalizer(JSRuntime* rt, JSValue val) { - auto* event = static_cast(JS_GetOpaque(val, Event::kEventClassID)); - delete event; -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/event.h b/bridge/bindings/qjs/dom/event.h deleted file mode 100644 index 334d422171..0000000000 --- a/bridge/bindings/qjs/dom/event.h +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_EVENT_H -#define KRAKENBRIDGE_EVENT_H - -#include "bindings/qjs/host_class.h" - -namespace kraken::binding::qjs { - -#define EVENT_CLICK "click" -#define EVENT_INPUT "input" -#define EVENT_APPEAR "appear" -#define EVENT_DISAPPEAR "disappear" -#define EVENT_COLOR_SCHEME_CHANGE "colorschemechange" -#define EVENT_ERROR "error" -#define EVENT_MEDIA_ERROR "mediaerror" -#define EVENT_TOUCH_START "touchstart" -#define EVENT_TOUCH_MOVE "touchmove" -#define EVENT_TOUCH_END "touchend" -#define EVENT_TOUCH_CANCEL "touchcancel" -#define EVENT_MESSAGE "message" -#define EVENT_CLOSE "close" -#define EVENT_OPEN "open" -#define EVENT_INTERSECTION_CHANGE "intersectionchange" -#define EVENT_CANCEL "cancel" -#define EVENT_POPSTATE "popstate" -#define EVENT_FINISH "finish" -#define EVENT_TRANSITION_RUN "transitionrun" -#define EVENT_TRANSITION_CANCEL "transitioncancel" -#define EVENT_TRANSITION_START "transitionstart" -#define EVENT_TRANSITION_END "transitionend" -#define EVENT_FOCUS "focus" -#define EVENT_LOAD "load" -#define EVENT_UNLOAD "unload" -#define EVENT_CHANGE "change" -#define EVENT_CAN_PLAY "canplay" -#define EVENT_CAN_PLAY_THROUGH "canplaythrough" -#define EVENT_ENDED "ended" -#define EVENT_PAUSE "pause" -#define EVENT_PLAY "play" -#define EVENT_SEEKED "seeked" -#define EVENT_SEEKING "seeking" -#define EVENT_VOLUME_CHANGE "volumechange" -#define EVENT_SCROLL "scroll" -#define EVENT_SWIPE "swipe" -#define EVENT_PAN "pan" -#define EVENT_LONG_PRESS "longpress" -#define EVENT_SCALE "scale" - -void bindEvent(ExecutionContext* context); - -class EventInstance; -class EventTargetInstance; -class NativeEventTarget; - -using EventCreator = EventInstance* (*)(ExecutionContext* context, void* nativeEvent); - -class Event : public HostClass { - public: - static JSClassID kEventClassID; - - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - Event() = delete; - explicit Event(ExecutionContext* context); - - static EventInstance* buildEventInstance(std::string& eventType, ExecutionContext* context, void* nativeEvent, bool isCustomEvent); - static void defineEvent(const std::string& eventType, EventCreator creator); - - OBJECT_INSTANCE(Event); - - static JSValue stopPropagation(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue stopImmediatePropagation(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue preventDefault(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue initEvent(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - private: - static std::unordered_map m_eventCreatorMap; - - DEFINE_PROTOTYPE_READONLY_PROPERTY(type); - DEFINE_PROTOTYPE_READONLY_PROPERTY(bubbles); - DEFINE_PROTOTYPE_READONLY_PROPERTY(cancelable); - DEFINE_PROTOTYPE_READONLY_PROPERTY(timestamp); - DEFINE_PROTOTYPE_READONLY_PROPERTY(defaultPrevented); - DEFINE_PROTOTYPE_READONLY_PROPERTY(target); - DEFINE_PROTOTYPE_READONLY_PROPERTY(srcElement); - DEFINE_PROTOTYPE_READONLY_PROPERTY(currentTarget); - DEFINE_PROTOTYPE_READONLY_PROPERTY(returnValue); - DEFINE_PROTOTYPE_READONLY_PROPERTY(cancelBubble); - - DEFINE_PROTOTYPE_FUNCTION(stopPropagation, 0); - DEFINE_PROTOTYPE_FUNCTION(stopImmediatePropagation, 0); - DEFINE_PROTOTYPE_FUNCTION(preventDefault, 1); - DEFINE_PROTOTYPE_FUNCTION(initEvent, 3); - - friend EventInstance; -}; - -// Dart generated nativeEvent member are force align to 64-bit system. So all members in NativeEvent should have 64 bit width. -#if ANDROID_32_BIT -struct NativeEvent { - int64_t type{0}; - int64_t bubbles{0}; - int64_t cancelable{0}; - int64_t timeStamp{0}; - int64_t defaultPrevented{0}; - // The pointer address of target EventTargetInstance object. - int64_t target{0}; - // The pointer address of current target EventTargetInstance object. - int64_t currentTarget{0}; -}; -#else -// Use pointer instead of int64_t on 64 bit system can help compiler to choose best register for better running performance. -struct NativeEvent { - NativeString* type{nullptr}; - int64_t bubbles{0}; - int64_t cancelable{0}; - int64_t timeStamp{0}; - int64_t defaultPrevented{0}; - // The pointer address of target EventTargetInstance object. - void* target{nullptr}; - // The pointer address of current target EventTargetInstance object. - void* currentTarget{nullptr}; -}; -#endif - -struct RawEvent { - uint64_t* bytes; - int64_t length; -}; - -class EventInstance : public Instance { - public: - EventInstance() = delete; - ~EventInstance() override { delete nativeEvent; } - - static EventInstance* fromNativeEvent(Event* event, NativeEvent* nativeEvent); - NativeEvent* nativeEvent{nullptr}; - - FORCE_INLINE const bool propagationStopped() { return m_propagationStopped; } - FORCE_INLINE const bool cancelled() { return m_cancelled; } - FORCE_INLINE void cancelled(bool v) { m_cancelled = v; } - FORCE_INLINE const bool propagationImmediatelyStopped() { return m_propagationImmediatelyStopped; } - FORCE_INLINE NativeString* type() { -#if ANDROID_32_BIT - return reinterpret_cast(nativeEvent->type); -#else - return nativeEvent->type; -#endif - }; - void setType(NativeString* type) const; - EventTargetInstance* target() const; - void setTarget(EventTargetInstance* target) const; - EventTargetInstance* currentTarget() const; - void setCurrentTarget(EventTargetInstance* target) const; - - protected: - explicit EventInstance(Event* jsEvent, JSAtom eventType, JSValue eventInit); - explicit EventInstance(Event* jsEvent, NativeEvent* nativeEvent); - bool m_cancelled{false}; - bool m_propagationStopped{false}; - bool m_propagationImmediatelyStopped{false}; - - private: - static void finalizer(JSRuntime* rt, JSValue val); - friend Event; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_EVENT_H diff --git a/bridge/bindings/qjs/dom/event_listener_map.cc b/bridge/bindings/qjs/dom/event_listener_map.cc deleted file mode 100644 index 55cac80bfa..0000000000 --- a/bridge/bindings/qjs/dom/event_listener_map.cc +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "event_listener_map.h" - -namespace kraken::binding::qjs { - -static bool addListenerToVector(EventListenerVector* vector, JSValue callback) { - if (std::find_if(vector->begin(), vector->end(), [&callback](JSValue fn) { return JS_VALUE_GET_PTR(fn) == JS_VALUE_GET_PTR(callback); }) != vector->end()) { - return false; // Duplicate listener. - } - - vector->push_back(callback); - return true; -} - -static bool removeListenerFromVector(EventListenerVector* listenerVector, JSValue callback) { - // Do a manual search for the matching listener. It is not - // possible to create a listener on the stack because of the - // const on |listener|. - auto it = std::find_if(listenerVector->begin(), listenerVector->end(), [&callback](const JSValue& listener) -> bool { return JS_VALUE_GET_PTR(listener) == JS_VALUE_GET_PTR(callback); }); - - if (it == listenerVector->end()) { - return false; - } - listenerVector->erase(it); - return true; -} - -bool EventListenerMap::contains(JSAtom eventType) const { - for (const auto& entry : m_entries) { - if (entry.first == eventType) - return true; - } - return false; -} - -void EventListenerMap::clear() { - m_entries.clear(); -} - -bool EventListenerMap::add(JSAtom eventType, JSValue callback) { - for (const auto& entry : m_entries) { - if (entry.first == eventType) { - return addListenerToVector(const_cast(&entry.second), callback); - } - } - - std::vector list; - list.reserve(8); - m_entries.emplace_back(std::make_pair(eventType, list)); - - return addListenerToVector(&m_entries.back().second, callback); -} - -bool EventListenerMap::remove(JSAtom eventType, JSValue callback) { - for (unsigned i = 0; i < m_entries.size(); ++i) { - if (m_entries[i].first == eventType) { - bool was_removed = removeListenerFromVector(&m_entries[i].second, callback); - if (m_entries[i].second.empty()) { - m_entries.erase(m_entries.begin() + i); - } - return was_removed; - } - } - - return false; -} - -const EventListenerVector* EventListenerMap::find(JSAtom eventType) { - for (const auto& entry : m_entries) { - if (entry.first == eventType) - return &entry.second; - } - - return nullptr; -} - -void EventListenerMap::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - for (const auto& entry : m_entries) { - for (const auto& vector : entry.second) { - JS_MarkValue(rt, vector, mark_func); - } - } -} - -EventListenerMap::~EventListenerMap() { - for (const auto& entry : m_entries) { - for (const auto& vector : entry.second) { - JS_FreeAtomRT(m_runtime, entry.first); - JS_FreeValueRT(m_runtime, vector); - } - } -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/event_listener_map.h b/bridge/bindings/qjs/dom/event_listener_map.h deleted file mode 100644 index 7a0be8604c..0000000000 --- a/bridge/bindings/qjs/dom/event_listener_map.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ -#define KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ - -#include -#include -#include "include/kraken_foundation.h" - -namespace kraken::binding::qjs { - -using EventListenerVector = std::vector; - -class EventListenerMap final { - public: - EventListenerMap(JSContext* ctx) : m_runtime(JS_GetRuntime(ctx)){}; - ~EventListenerMap(); - - [[nodiscard]] bool empty() const { return m_entries.empty(); } - [[nodiscard]] bool contains(JSAtom eventType) const; - void clear(); - bool add(JSAtom eventType, JSValue callback); - bool remove(JSAtom eventType, JSValue callback); - const EventListenerVector* find(JSAtom eventType); - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func); - - private: - // EventListener handlers registered with addEventListener API. - // We use vector instead of hashMap because - // - vector is much more space efficient than hashMap. - // - An EventTarget rarely has event listeners for many event types, and - // vector is faster in such cases. - std::vector> m_entries; - - JSRuntime* m_runtime; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ diff --git a/bridge/bindings/qjs/dom/event_target.cc b/bridge/bindings/qjs/dom/event_target.cc deleted file mode 100644 index 05931e6777..0000000000 --- a/bridge/bindings/qjs/dom/event_target.cc +++ /dev/null @@ -1,528 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "event_target.h" - -#include -#include "bindings/qjs/bom/window.h" -#include "bindings/qjs/dom/text_node.h" -#include "bindings/qjs/qjs_patch.h" -#include "document.h" -#include "element.h" -#include "event.h" -#include "kraken_bridge.h" - -#define PROPAGATION_STOPPED 1 -#define PROPAGATION_CONTINUE 0 - -#if UNIT_TEST -#include "kraken_test_env.h" -#endif - -namespace kraken::binding::qjs { - -static std::atomic globalEventTargetId{0}; -std::once_flag kEventTargetInitFlag; - -void bindEventTarget(ExecutionContext* context) { - auto* constructor = EventTarget::instance(context); - // Set globalThis and Window's prototype to EventTarget's prototype to support EventTarget methods in global. - JS_SetPrototype(context->ctx(), context->global(), constructor->jsObject); - context->defineGlobalProperty("EventTarget", constructor->jsObject); -} - -JSClassID EventTarget::kEventTargetClassId{0}; - -EventTarget::EventTarget(ExecutionContext* context, const char* name) : HostClass(context, name) {} -EventTarget::EventTarget(ExecutionContext* context) : HostClass(context, "EventTarget") { - std::call_once(kEventTargetInitFlag, []() { JS_NewClassID(&kEventTargetClassId); }); -} - -JSValue EventTarget::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - auto eventTarget = new EventTargetInstance(this, kEventTargetClassId, "EventTarget"); - return eventTarget->jsObject; -} - -JSClassID EventTarget::classId() { - assert_m(false, "classId is not implemented"); - return 0; -} - -JSClassID EventTarget::classId(JSValue& value) { - JSClassID classId = JSValueGetClassId(value); - return classId; -} - -JSValue EventTarget::addEventListener(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 2) { - return JS_ThrowTypeError(ctx, "Failed to addEventListener: type and listener are required."); - } - - auto* eventTargetInstance = static_cast(JS_GetOpaque(this_val, EventTarget::classId(this_val))); - if (eventTargetInstance == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to addEventListener: this is not an EventTarget object."); - } - - JSValue eventTypeValue = argv[0]; - JSValue callback = argv[1]; - - if (!JS_IsString(eventTypeValue) || !JS_IsObject(callback) || !JS_IsFunction(ctx, callback)) { - return JS_UNDEFINED; - } - - // EventType atom will be freed when eventTarget finalized. - JSAtom eventType = JS_ValueToAtom(ctx, eventTypeValue); - - // Dart needs to be notified for the first registration event. - if (!eventTargetInstance->m_eventListenerMap.contains(eventType) && !eventTargetInstance->m_eventHandlerMap.contains(eventType)) { - int32_t contextId = eventTargetInstance->prototype()->contextId(); - - NativeString args_01{}; - buildUICommandArgs(ctx, eventTypeValue, args_01); - - eventTargetInstance->m_context->uiCommandBuffer()->addCommand(eventTargetInstance->m_eventTargetId, UICommand::addEvent, args_01, nullptr); - } - - bool success = eventTargetInstance->m_eventListenerMap.add(eventType, JS_DupValue(ctx, callback)); - // Callback didn't saved to eventListenerMap. - if (!success) { - JS_FreeAtom(ctx, eventType); - JS_FreeValue(ctx, callback); - } - - return JS_UNDEFINED; -} - -JSValue EventTarget::removeEventListener(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 2) { - return JS_ThrowTypeError(ctx, "Failed to removeEventListener: at least type and listener are required."); - } - - auto* eventTargetInstance = static_cast(JS_GetOpaque(this_val, EventTarget::classId(this_val))); - if (eventTargetInstance == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to addEventListener: this is not an EventTarget object."); - } - - JSValue eventTypeValue = argv[0]; - JSValue callback = argv[1]; - - if (!JS_IsString(eventTypeValue)) { - return JS_ThrowTypeError(ctx, "Failed to removeEventListener: eventName should be an string."); - } - - if (!JS_IsObject(callback) || !JS_IsObject(callback)) { - return JS_ThrowTypeError(ctx, "Failed to removeEventListener: eventHandler should be an object or function."); - } - - JSAtom eventType = JS_ValueToAtom(ctx, eventTypeValue); - auto& eventHandlers = eventTargetInstance->m_eventListenerMap; - - if (!eventTargetInstance->m_eventListenerMap.contains(eventType)) { - JS_FreeAtom(ctx, eventType); - return JS_UNDEFINED; - } - - if (eventHandlers.remove(eventType, callback)) { - JS_FreeAtom(ctx, eventType); - JS_FreeValue(ctx, callback); - } - - if (!eventHandlers.contains(eventType) && !eventTargetInstance->m_eventHandlerMap.contains(eventType)) { - // Dart needs to be notified for handles is empty. - int32_t contextId = eventTargetInstance->prototype()->contextId(); - - NativeString args_01{}; - buildUICommandArgs(ctx, eventTypeValue, args_01); - - eventTargetInstance->m_context->uiCommandBuffer()->addCommand(eventTargetInstance->m_eventTargetId, UICommand::removeEvent, args_01, nullptr); - } - - JS_FreeAtom(ctx, eventType); - return JS_UNDEFINED; -} - -JSValue EventTarget::dispatchEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc != 1) { - return JS_ThrowTypeError(ctx, "Failed to dispatchEvent: first arguments should be an event object"); - } - - auto* eventTargetInstance = static_cast(JS_GetOpaque(this_val, EventTarget::classId(this_val))); - if (eventTargetInstance == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to addEventListener: this is not an EventTarget object."); - } - - JSValue eventValue = argv[0]; - auto eventInstance = reinterpret_cast(JS_GetOpaque(eventValue, EventTarget::classId(eventValue))); -#if ANDROID_32_BIT - eventInstance->nativeEvent->target = reinterpret_cast(eventTargetInstance); -#else - eventInstance->nativeEvent->target = eventTargetInstance; -#endif - return JS_NewBool(ctx, eventTargetInstance->dispatchEvent(eventInstance)); -} - -bool EventTargetInstance::dispatchEvent(EventInstance* event) { - auto* pEventType = reinterpret_cast(event->nativeEvent->type); - - std::u16string u16EventType = std::u16string(reinterpret_cast(pEventType->string), pEventType->length); - std::string eventType = toUTF8(u16EventType); - - // protect this util event trigger finished. - JS_DupValue(m_ctx, jsObject); - - internalDispatchEvent(event); - - JS_FreeValue(m_ctx, jsObject); - - return event->cancelled(); -} - -bool EventTargetInstance::internalDispatchEvent(EventInstance* eventInstance) { - std::u16string u16EventType = std::u16string(reinterpret_cast(eventInstance->type()->string), eventInstance->type()->length); - std::string eventTypeStr = toUTF8(u16EventType); - JSAtom eventType = JS_NewAtom(m_ctx, eventTypeStr.c_str()); - - // Modify the currentTarget to this. - eventInstance->setCurrentTarget(this); - - // Dispatch event listeners writen by addEventListener - auto _dispatchEvent = [&eventInstance, this](JSValue handler) { - if (!JS_IsFunction(m_ctx, handler)) - return; - - if (eventInstance->propagationImmediatelyStopped()) - return; - - /* 'handler' might be destroyed when calling itself (if it frees the - handler), so must take extra care */ - JS_DupValue(m_ctx, handler); - - // The third params `thisObject` to null equals global object. - JSValue returnedValue = JS_Call(m_ctx, handler, JS_NULL, 1, &eventInstance->jsObject); - - JS_FreeValue(m_ctx, handler); - m_context->handleException(&returnedValue); - m_context->drainPendingPromiseJobs(); - JS_FreeValue(m_ctx, returnedValue); - }; - - // Dispatch event listener white by 'on' prefix property. - if (m_eventHandlerMap.contains(eventType)) { - auto* window = static_cast(JS_GetOpaque(context()->global(), 1)); - // Let special error event handling be true if event is an ErrorEvent. - bool specialErrorEventHanding = eventTypeStr == "error" && eventInstance->currentTarget() == window; - - if (specialErrorEventHanding) { - auto _dispatchErrorEvent = [&eventInstance, this, eventTypeStr](JSValue handler) { - JSValue error = JS_GetPropertyStr(m_ctx, eventInstance->jsObject, "error"); - JSValue messageValue = JS_GetPropertyStr(m_ctx, error, "message"); - JSValue lineNumberValue = JS_GetPropertyStr(m_ctx, error, "lineNumber"); - JSValue fileNameValue = JS_GetPropertyStr(m_ctx, error, "fileName"); - JSValue columnValue = JS_NewUint32(m_ctx, 0); - - JSValue args[]{messageValue, fileNameValue, lineNumberValue, columnValue, error}; - JSValue returnValue = JS_Call(m_ctx, handler, eventInstance->jsObject, 5, args); - m_context->drainPendingPromiseJobs(); - m_context->handleException(&returnValue); - - JS_FreeValue(m_ctx, error); - JS_FreeValue(m_ctx, messageValue); - JS_FreeValue(m_ctx, fileNameValue); - JS_FreeValue(m_ctx, lineNumberValue); - JS_FreeValue(m_ctx, columnValue); - }; - _dispatchErrorEvent(m_eventHandlerMap.getProperty(eventType)); - } else { - _dispatchEvent(m_eventHandlerMap.getProperty(eventType)); - } - } - - // Dispatch DOM Event Level 0 handlers. - if (m_eventListenerMap.contains(eventType)) { - const EventListenerVector* vector = m_eventListenerMap.find(eventType); - for (auto& eventHandler : *vector) { - _dispatchEvent(eventHandler); - } - } - - JS_FreeAtom(m_ctx, eventType); - - // do not dispatch event when event has been canceled - // true is prevented. - return eventInstance->cancelled(); -} - -EventTargetInstance::EventTargetInstance(EventTarget* eventTarget, JSClassID classId, JSClassExoticMethods& exoticMethods, std::string name) - : Instance(eventTarget, name, &exoticMethods, classId, finalize) { - m_eventTargetId = globalEventTargetId++; -} - -EventTargetInstance::EventTargetInstance(EventTarget* eventTarget, JSClassID classId, std::string name) : Instance(eventTarget, std::move(name), nullptr, classId, finalize) { - m_eventTargetId = globalEventTargetId++; -} - -EventTargetInstance::EventTargetInstance(EventTarget* eventTarget, JSClassID classId, std::string name, int64_t eventTargetId) - : Instance(eventTarget, std::move(name), nullptr, classId, finalize), m_eventTargetId(eventTargetId) {} - -JSClassID EventTargetInstance::classId() { - assert_m(false, "classId is not implemented"); - return 0; -} - -EventTargetInstance::~EventTargetInstance() { -#if UNIT_TEST - // Callback to unit test specs before eventTarget finalized. - if (TEST_getEnv(m_context->uniqueId)->onEventTargetDisposed != nullptr) { - TEST_getEnv(m_context->uniqueId)->onEventTargetDisposed(this); - } -#endif - - m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::disposeEventTarget, nullptr, false); - getDartMethod()->flushUICommand(); - delete nativeEventTarget; -} - -int EventTargetInstance::hasProperty(JSContext* ctx, JSValue obj, JSAtom atom) { - auto* eventTarget = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); - auto* prototype = static_cast(eventTarget->prototype()); - - if (JS_HasProperty(ctx, prototype->m_prototypeObject, atom)) - return true; - - JSValue atomString = JS_AtomToString(ctx, atom); - JSString* p = JS_VALUE_GET_STRING(atomString); - // There are still one reference_count in atom. It's safe to free here. - JS_FreeValue(ctx, atomString); - - if (!p->is_wide_char && p->u.str8[0] == 'o' && p->u.str8[1] == 'n') { - const char* eventTypeName = reinterpret_cast(p->u.str8); - if (EventTypeNames::isEventTypeName(eventTypeName)) { - return true; - } - - return !JS_IsNull(eventTarget->getAttributesEventHandler(p)); - } - - return eventTarget->m_properties.contains(atom); -} - -JSValue EventTargetInstance::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { - auto* eventTarget = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); - JSValue prototype = JS_GetPrototype(ctx, eventTarget->jsObject); - if (JS_HasProperty(ctx, prototype, atom)) { - JSValue ret = JS_GetPropertyInternal(ctx, prototype, atom, eventTarget->jsObject, 0); - JS_FreeValue(ctx, prototype); - return ret; - } - JS_FreeValue(ctx, prototype); - - JSValue atomString = JS_AtomToString(ctx, atom); - JSString* p = JS_VALUE_GET_STRING(atomString); - // There are still one reference_count in atom. It's safe to free here. - JS_FreeValue(ctx, atomString); - - if (!p->is_wide_char && p->u.str8[0] == 'o' && p->u.str8[1] == 'n') { - return eventTarget->getAttributesEventHandler(p); - } - - if (eventTarget->m_properties.contains(atom)) { - return JS_DupValue(ctx, eventTarget->m_properties.getProperty(atom)); - } - - // For plugin elements, try to auto generate properties and functions from dart response. - if (isJavaScriptExtensionElementInstance(eventTarget->context(), eventTarget->jsObject)) { - const char* cmethod = JS_AtomToCString(eventTarget->m_ctx, atom); - // Property starts with underscore are taken as private property in javascript object. - if (cmethod[0] == '_') { - JS_FreeCString(eventTarget->m_ctx, cmethod); - return JS_UNDEFINED; - } - JSValue result = eventTarget->getBindingProperty(cmethod); - JS_FreeCString(ctx, cmethod); - return result; - } - - return JS_UNDEFINED; -} - -int EventTargetInstance::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { - auto* eventTarget = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); - JSValue prototype = JS_GetPrototype(ctx, eventTarget->jsObject); - - // Check there are setter functions on prototype. - if (JS_HasProperty(ctx, prototype, atom)) { - // Read setter function from prototype Object. - JSPropertyDescriptor descriptor; - JS_GetOwnProperty(ctx, &descriptor, prototype, atom); - JSValue setterFunc = descriptor.setter; - assert_m(JS_IsFunction(ctx, setterFunc), "Setter on prototype should be an function."); - JSValue ret = JS_Call(ctx, setterFunc, eventTarget->jsObject, 1, &value); - if (JS_IsException(ret)) - return -1; - - JS_FreeValue(ctx, ret); - JS_FreeValue(ctx, descriptor.setter); - JS_FreeValue(ctx, descriptor.getter); - JS_FreeValue(ctx, prototype); - return 1; - } - - JS_FreeValue(ctx, prototype); - - JSValue atomString = JS_AtomToString(ctx, atom); - JSString* p = JS_VALUE_GET_STRING(atomString); - - if (!p->is_wide_char && p->len > 2 && p->u.str8[0] == 'o' && p->u.str8[1] == 'n') { - eventTarget->setAttributesEventHandler(p, value); - } else { - eventTarget->m_properties.setProperty(JS_DupAtom(ctx, atom), JS_DupValue(ctx, value)); - if (isJavaScriptExtensionElementInstance(eventTarget->context(), eventTarget->jsObject) && !p->is_wide_char && p->u.str8[0] != '_') { - std::unique_ptr args_01 = atomToNativeString(ctx, atom); - std::unique_ptr args_02 = jsValueToNativeString(ctx, value); - eventTarget->m_context->uiCommandBuffer()->addCommand(eventTarget->m_eventTargetId, UICommand::setAttribute, *args_01, *args_02, nullptr); - } - } - - JS_FreeValue(ctx, atomString); - - return 0; -} - -int EventTargetInstance::deleteProperty(JSContext* ctx, JSValue obj, JSAtom prop) { - return 0; -} - -JSValue EventTargetInstance::invokeBindingMethod(const char* method, int32_t argc, NativeValue* argv) { - if (nativeEventTarget->invokeBindingMethod == nullptr) { - return JS_ThrowTypeError(m_ctx, "Failed to call dart method: invokeBindingMethod not initialized."); - } - - std::u16string methodString; - fromUTF8(method, methodString); - - NativeString m{reinterpret_cast(methodString.c_str()), static_cast(methodString.size())}; - - NativeValue nativeValue{}; - nativeEventTarget->invokeBindingMethod(nativeEventTarget, &nativeValue, &m, argc, argv); - JSValue returnValue = nativeValueToJSValue(m_context, nativeValue); - return returnValue; -} - -void EventTargetInstance::setAttributesEventHandler(JSString* p, JSValue value) { - char eventType[p->len + 1 - 2]; - memcpy(eventType, &p->u.str8[2], p->len + 1 - 2); - JSAtom atom = JS_NewAtom(m_ctx, eventType); - - enum SetAttributeEventHandlerOperation { kAddEventListener, kRemoveEventListener, kDoNothing }; - - SetAttributeEventHandlerOperation operation = kDoNothing; - if (JS_IsFunction(m_ctx, value)) { - operation = SetAttributeEventHandlerOperation::kAddEventListener; - } else { - if (m_eventHandlerMap.contains(atom)) { - // When evaluate scripts like 'element.onclick = null', we needs to remove the event handlers callbacks. - operation = SetAttributeEventHandlerOperation::kRemoveEventListener; - m_eventHandlerMap.erase(atom); - } - JS_FreeAtom(m_ctx, atom); - } - - if (operation != kDoNothing && !m_eventListenerMap.contains(atom) && !m_eventHandlerMap.contains(atom)) { - std::unique_ptr args_01 = atomToNativeString(m_ctx, atom); - UICommand type = operation == kAddEventListener ? UICommand::addEvent : UICommand::removeEvent; - m_context->uiCommandBuffer()->addCommand(m_eventTargetId, type, *args_01, nullptr); - } - - if (operation == SetAttributeEventHandlerOperation::kAddEventListener) { - m_eventHandlerMap.setProperty(atom, JS_DupValue(m_ctx, value)); - } -} - -JSValue EventTargetInstance::getAttributesEventHandler(JSString* p) { - char eventType[p->len + 1 - 2]; - memcpy(eventType, &p->u.str8[2], p->len + 1 - 2); - JSAtom atom = JS_NewAtom(m_ctx, eventType); - if (!m_eventHandlerMap.contains(atom)) { - JS_FreeAtom(m_ctx, atom); - return JS_NULL; - } - - JSValue handler = JS_DupValue(m_ctx, m_eventHandlerMap.getProperty(atom)); - JS_FreeAtom(m_ctx, atom); - return handler; -} - -void EventTargetInstance::finalize(JSRuntime* rt, JSValue val) { - auto* eventTarget = static_cast(JS_GetOpaque(val, EventTarget::classId(val))); - delete eventTarget; -} - -JSValue EventTargetInstance::getBindingProperty(const char* prop) { - getDartMethod()->flushUICommand(); - NativeValue args[] = {Native_NewCString(prop)}; - return invokeBindingMethod(GetPropertyMagic, 1, args); -} - -void EventTargetInstance::setBindingProperty(const char* prop, NativeValue value) { - // If not flush UICommands, the element may not be created. - getDartMethod()->flushUICommand(); - NativeValue args[] = {Native_NewCString(prop), value}; - invokeBindingMethod(SetPropertyMagic, 2, args); -} - -// JSValues are stored in this class are no visible to QuickJS GC. -// We needs to gc which JSValues are still holding. -void EventTargetInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - // Trace m_eventListeners. - m_eventListenerMap.trace(rt, JS_UNDEFINED, mark_func); - - // Trace m_eventHandlers. - m_eventHandlerMap.trace(rt, JS_UNDEFINED, mark_func); - - // Trace properties. - m_properties.trace(rt, JS_UNDEFINED, mark_func); -} - -void EventTargetInstance::copyNodeProperties(EventTargetInstance* newNode, EventTargetInstance* referenceNode) { - referenceNode->m_properties.copyWith(&newNode->m_properties); -} - -int32_t NativeEventTarget::dispatchEventImpl(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* nativeEventType, void* rawEvent, int32_t isCustomEvent) { - assert_m(nativeEventTarget->instance != nullptr, "NativeEventTarget should have owner"); - EventTargetInstance* eventTargetInstance = nativeEventTarget->instance; - - auto* runtime = ExecutionContext::runtime(); - - // Should avoid dispatch event is ctx is invalid. - if (!isContextValid(contextId)) { - return 1; - } - - // We should avoid trigger event if eventTarget are no long live on heap. - if (!JS_IsLiveObject(runtime, eventTargetInstance->jsObject)) { - return 1; - } - - ExecutionContext* context = eventTargetInstance->context(); - std::u16string u16EventType = std::u16string(reinterpret_cast(nativeEventType->string), nativeEventType->length); - std::string eventType = toUTF8(u16EventType); - auto* raw = static_cast(rawEvent); - // NativeEvent members are memory aligned corresponding to NativeEvent. - // So we can reinterpret_cast raw bytes pointer to NativeEvent type directly. - auto* nativeEvent = reinterpret_cast(raw->bytes); - EventInstance* eventInstance = Event::buildEventInstance(eventType, context, nativeEvent, isCustomEvent == 1); - - eventTargetInstance->dispatchEvent(eventInstance); - - bool propagationStopped = eventInstance->propagationStopped(); - - JS_FreeValue(context->ctx(), eventInstance->jsObject); - - // FIXME: The return value is first propagationStopped instead of cancelable, and then implement a separate method to synchronize propagationStopped. - // Dispatches a synthetic event event to target and returns true if either event’s cancelable attribute value is false or its preventDefault() method was not invoked; otherwise false. - // https://dom.spec.whatwg.org/#ref-for-dom-eventtarget-dispatchevent%E2%91%A2 - return propagationStopped ? PROPAGATION_STOPPED : PROPAGATION_CONTINUE; -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/event_target.h b/bridge/bindings/qjs/dom/event_target.h deleted file mode 100644 index 9248bc8e5a..0000000000 --- a/bridge/bindings/qjs/dom/event_target.h +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_EVENT_TARGET_H -#define KRAKENBRIDGE_EVENT_TARGET_H - -#include "bindings/qjs/dom/event.h" -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/heap_hashmap.h" -#include "bindings/qjs/host_class.h" -#include "bindings/qjs/host_object.h" -#include "bindings/qjs/native_value.h" -#include "bindings/qjs/qjs_patch.h" -#include "event_listener_map.h" -#include "event_type_names.h" - -#if UNIT_TEST -void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); -#endif - -#define GetPropertyMagic "%g" -#define SetPropertyMagic "%s" - -namespace kraken::binding::qjs { - -class EventTargetInstance; -class NativeEventTarget; -class CSSStyleDeclaration; -class StyleDeclarationInstance; - -void bindEventTarget(ExecutionContext* context); - -class EventTarget : public HostClass { - public: - static JSClassID kEventTargetClassId; - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - EventTarget() = delete; - explicit EventTarget(ExecutionContext* context, const char* name); - explicit EventTarget(ExecutionContext* context); - - static JSClassID classId(); - static JSClassID classId(JSValue& value); - - OBJECT_INSTANCE(EventTarget); - - private: - static JSValue addEventListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue removeEventListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue dispatchEvent(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - DEFINE_PROTOTYPE_FUNCTION(addEventListener, 3); - DEFINE_PROTOTYPE_FUNCTION(removeEventListener, 2); - DEFINE_PROTOTYPE_FUNCTION(dispatchEvent, 1); - friend EventTargetInstance; -}; - -using NativeDispatchEvent = int32_t (*)(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); -using InvokeBindingMethod = void (*)(void* nativePtr, NativeValue* returnValue, NativeString* method, int32_t argc, NativeValue* argv); - -struct NativeEventTarget { - NativeEventTarget() = delete; - explicit NativeEventTarget(EventTargetInstance* _instance) : instance(_instance), dispatchEvent(reinterpret_cast(NativeEventTarget::dispatchEventImpl)){}; - - // Add more memory valid check with contextId. - static int32_t dispatchEventImpl(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); - EventTargetInstance* instance{nullptr}; - NativeDispatchEvent dispatchEvent{nullptr}; -#if UNIT_TEST - InvokeBindingMethod invokeBindingMethod{reinterpret_cast(TEST_invokeBindingMethod)}; -#else - InvokeBindingMethod invokeBindingMethod{nullptr}; -#endif -}; - -class EventTargetProperties : public HeapHashMap { - public: - EventTargetProperties(JSContext* ctx) : HeapHashMap(ctx){}; -}; - -class EventHandlerMap : public HeapHashMap { - public: - EventHandlerMap(JSContext* ctx) : HeapHashMap(ctx){}; -}; - -class EventTargetInstance : public Instance { - public: - EventTargetInstance() = delete; - explicit EventTargetInstance(EventTarget* eventTarget, JSClassID classId, JSClassExoticMethods& exoticMethods, std::string name); - explicit EventTargetInstance(EventTarget* eventTarget, JSClassID classId, std::string name); - explicit EventTargetInstance(EventTarget* eventTarget, JSClassID classId, std::string name, int64_t eventTargetId); - ~EventTargetInstance(); - - virtual bool dispatchEvent(EventInstance* event); - static inline JSClassID classId(); - inline int32_t eventTargetId() const { return m_eventTargetId; } - - // @TODO: Should move to BindingObject. - JSValue invokeBindingMethod(const char* method, int32_t argc, NativeValue* argv); - JSValue getBindingProperty(const char* prop); - void setBindingProperty(const char* prop, NativeValue value); - - NativeEventTarget* nativeEventTarget{new NativeEventTarget(this)}; - - protected: - int32_t m_eventTargetId; - // EventListener handlers registered with addEventListener API. - // https://dom.spec.whatwg.org/#concept-event-listener - EventListenerMap m_eventListenerMap{m_ctx}; - - // EventListener handlers registered with DOM attributes API. - // https://html.spec.whatwg.org/C/#event-handler-attributes - EventHandlerMap m_eventHandlerMap{m_ctx}; - - // When javascript code set a property on EventTarget instance, EventTarget::setAttribute callback will be called when - // property are not defined by Object.defineProperty or setAttribute. - // We store there values in here. - EventTargetProperties m_properties{m_ctx}; - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; - static void copyNodeProperties(EventTargetInstance* newNode, EventTargetInstance* referenceNode); - - static int hasProperty(JSContext* ctx, JSValueConst obj, JSAtom atom); - static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); - static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); - static int deleteProperty(JSContext* ctx, JSValueConst obj, JSAtom prop); - - // Used for legacy "onEvent" attribute APIs. - void setAttributesEventHandler(JSString* p, JSValue value); - JSValue getAttributesEventHandler(JSString* p); - - private: - bool internalDispatchEvent(EventInstance* eventInstance); - static void finalize(JSRuntime* rt, JSValue val); - friend EventTarget; - friend StyleDeclarationInstance; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_EVENT_TARGET_H diff --git a/bridge/bindings/qjs/dom/event_target_test.cc b/bridge/bindings/qjs/dom/event_target_test.cc deleted file mode 100644 index 2b51b82fb5..0000000000 --- a/bridge/bindings/qjs/dom/event_target_test.cc +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "event_target.h" -#include "gtest/gtest.h" -#include "kraken_test_env.h" -#include "page.h" - -TEST(EventTarget, addEventListener) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - EXPECT_STREQ(message.c_str(), "1234"); - logCalled = true; - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f); div.dispatchEvent(new Event('click'));"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); -} - -TEST(EventTarget, removeEventListener) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = - "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f); div.removeEventListener('click', f); div.dispatchEvent(new Event('click'));"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(logCalled, false); -} - -TEST(EventTarget, setNoEventTargetProperties) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "{name: 1}"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - - auto context = bridge->getContext(); - const char* code = "let div = document.createElement('div'); div._a = { name: 1}; console.log(div._a); document.body.appendChild(div);"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); -} - -TEST(EventTarget, propertyEventHandler) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "ƒ () 1234"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = - "let div = document.createElement('div'); " - "div.onclick = function() { return 1234; };" - "document.body.appendChild(div);" - "let f = div.onclick;" - "console.log(f, div.onclick());"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, attributeEventHandlerShouldExit) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "true"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = - "let div = document.createElement('div'); " - "console.log('onclick' in div)"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, setUnExpectedAttributeEventHandler) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = false; }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = - "let div = document.createElement('div'); " - "div.onclick = function() { return 1234; };" - "document.body.appendChild(div);" - "div.onclick = undefined;" - "div.click()"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, false); -} - -TEST(EventTarget, propertyEventOnWindow) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "1234"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = - "window.onclick = function() { console.log(1234); };" - "window.dispatchEvent(new Event('click'));"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, asyncFunctionCallback) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "done"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - std::string code = R"( - const img = document.createElement('img'); - img.style.width = '100px'; - img.style.height = '100px'; - img.src = "assets/kraken.png"; - document.body.appendChild(img); - const img2 = img.cloneNode(false); - document.body.appendChild(img2); - - let anotherImgHasLoad = false; - async function loadImg() { - if (anotherImgHasLoad) { - console.log('done'); - } else { - anotherImgHasLoad = true; - } - } - - img.addEventListener('load', loadImg); - img2.addEventListener('load', loadImg); - - img.dispatchEvent(new Event('load')); - img2.dispatchEvent(new Event('load')); -)"; - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, ClassInheritEventTarget) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "ƒ () ƒ ()"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - std::string code = std::string(R"( -class Sample extends EventTarget { - constructor() { - super(); - } -} - -let s = new Sample(); -console.log(s.addEventListener, s.removeEventListener) -)"); - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, wontLeakWithStringProperty) { - auto bridge = TEST_init(); - std::string code = - "var img = new Image();\n" - "img.any = '1234'"; - bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); -} - -TEST(EventTarget, dispatchEventOnGC) { - using namespace kraken::binding::qjs; - - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "1234"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); - std::string code = std::string(R"( -{ -// Wrap div in a block scope will be freed by GC -let div = document.createElement('div'); -} -window.onclick = () => {console.log(1234);} - -setTimeout(() => {}); -)"); - - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - - static auto* window = static_cast(JS_GetOpaque(context->global(), 1)); - static int32_t contextId = context->getContextId(); - - TEST_registerEventTargetDisposedCallback(context->uniqueId, [](EventTargetInstance* eventTargetInstance) { - // Check to not crash when trigger click on disposed eventTarget - TEST_dispatchEvent(contextId, eventTargetInstance, "click"); - - // Check to not crash when trigger event on any eventTarget. - TEST_dispatchEvent(contextId, window, "click"); - }); - - // Run gc to trigger eventTarget been disposed by GC. - JS_RunGC(context->runtime()); - - TEST_runLoop(context); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, globalBindListener) { - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "clicked"); - }; - auto bridge = TEST_init(); - std::string code = "addEventListener('click', () => {console.log('clicked'); }); dispatchEvent(new Event('click'))"; - bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, shouldKeepAtom) { - auto bridge = TEST_init(); - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "2"); - }; - std::string code = "addEventListener('click', () => {console.log(1)});"; - bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); - JS_RunGC(bridge->getContext()->runtime()); - - std::string code2 = "addEventListener('appear', () => {console.log(2)});"; - bridge->evaluateScript(code2.c_str(), code2.size(), "internal://", 0); - - JS_RunGC(bridge->getContext()->runtime()); - - std::string code3 = "(function() { var eeee = new Event('appear'); dispatchEvent(eeee); } )();"; - bridge->evaluateScript(code3.c_str(), code3.size(), "internal://", 0); - EXPECT_EQ(logCalled, true); -} diff --git a/bridge/bindings/qjs/dom/event_type_names.cc b/bridge/bindings/qjs/dom/event_type_names.cc deleted file mode 100644 index 1d70053d97..0000000000 --- a/bridge/bindings/qjs/dom/event_type_names.cc +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "event_type_names.h" -#include -#include - -namespace kraken::binding::qjs { - -static std::vector eventTypeNames{ - "onabort", - "onanimationend", - "onanimationiteration", - "onanimationstart", - "onblur", - "oncancel", - "oncanplay", - "oncanplaythrough", - "onchange", - "onclick", - "ondblclick", - "ondrag", - "ondragend", - "ondragenter", - "ondragleave", - "ondragover", - "ondragstart", - "onended", - "onkeydown", - "onkeypress", - "onkeyup", - "onclose", - "onerror", - "oninput", - "onload", - "onmousedown", - "onmouseenter", - "onmouseleave", - "onmousemove", - "onmouseout", - "onmouseover", - "onmouseup", - "onpause", - "onplay", - "onplaying", - "onscroll", - "ontouchcancel", - "ontouchend", - "ontouchmove", - "ontouchstart", - "ontransitioncancel", - "ontransitionend", - "ontransitionrun", - "ontransitionstart", -}; - -bool qjs::EventTypeNames::isEventTypeName(const std::string& name) { - return std::find(eventTypeNames.begin(), eventTypeNames.end(), name) != eventTypeNames.end(); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/event_type_names.h b/bridge/bindings/qjs/dom/event_type_names.h deleted file mode 100644 index c30a5c1dd7..0000000000 --- a/bridge/bindings/qjs/dom/event_type_names.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_TYPE_NAMES_H_ -#define KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_TYPE_NAMES_H_ - -#include -#include - -namespace kraken::binding::qjs { -class EventTypeNames { - public: - static bool isEventTypeName(const std::string& name); -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_TYPE_NAMES_H_ diff --git a/bridge/bindings/qjs/dom/events/.gitignore b/bridge/bindings/qjs/dom/events/.gitignore deleted file mode 100644 index 514978282a..0000000000 --- a/bridge/bindings/qjs/dom/events/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.gen diff --git a/bridge/bindings/qjs/dom/events/media_error_event.d.ts b/bridge/bindings/qjs/dom/events/media_error_event.d.ts deleted file mode 100644 index 26ed878abc..0000000000 --- a/bridge/bindings/qjs/dom/events/media_error_event.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -interface Event {} -type int64 = number; - -interface MediaErrorEvent extends Event { - readonly code: int64; - readonly message: string; -} diff --git a/bridge/bindings/qjs/dom/events/message_event.d.ts b/bridge/bindings/qjs/dom/events/message_event.d.ts deleted file mode 100644 index 7f6a9ceae2..0000000000 --- a/bridge/bindings/qjs/dom/events/message_event.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -interface Event {} -type int64 = number; - -interface MessageEvent extends Event { - // @ts-ignore - readonly data: any; - readonly origin: string; -} diff --git a/bridge/bindings/qjs/dom/events/mouse_event.d.ts b/bridge/bindings/qjs/dom/events/mouse_event.d.ts deleted file mode 100644 index 92c974e558..0000000000 --- a/bridge/bindings/qjs/dom/events/mouse_event.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -interface Event {} -type int64 = number; - -interface MouseEvent extends Event { - readonly clientX: number; - readonly clientY: number; - readonly offsetX: number; - readonly offsetY: number; -} diff --git a/bridge/bindings/qjs/dom/events/popstate_event.d.ts b/bridge/bindings/qjs/dom/events/popstate_event.d.ts deleted file mode 100644 index 54308eeebc..0000000000 --- a/bridge/bindings/qjs/dom/events/popstate_event.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -interface Event {} - -interface PopStateEvent extends Event { - readonly state: any; -} diff --git a/bridge/bindings/qjs/dom/frame_request_callback_collection.cc b/bridge/bindings/qjs/dom/frame_request_callback_collection.cc deleted file mode 100644 index 372fc38279..0000000000 --- a/bridge/bindings/qjs/dom/frame_request_callback_collection.cc +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "frame_request_callback_collection.h" - -namespace kraken::binding::qjs { - -JSClassID FrameCallback::classId{0}; -FrameCallback::FrameCallback(JSValue callback) : m_callback(callback) {} - -void FrameCallback::fire(double highResTimeStamp) { - auto* context = static_cast(JS_GetContextOpaque(m_ctx)); - if (!JS_IsFunction(m_ctx, m_callback)) - return; - - /* 'callback' might be destroyed when calling itself (if it frees the - handler), so must take extra care */ - JS_DupValue(m_ctx, m_callback); - - JSValue arguments[] = {JS_NewFloat64(m_ctx, highResTimeStamp)}; - - JSValue returnValue = JS_Call(m_ctx, m_callback, JS_UNDEFINED, 1, arguments); - - context->drainPendingPromiseJobs(); - JS_FreeValue(m_ctx, m_callback); - - if (JS_IsException(returnValue)) { - context->handleException(&returnValue); - } - - JS_FreeValue(m_ctx, returnValue); -} - -void FrameCallback::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - JS_MarkValue(rt, m_callback, mark_func); -} - -void FrameCallback::dispose() const { - JS_FreeValueRT(m_runtime, m_callback); -} - -void FrameRequestCallbackCollection::registerFrameCallback(uint32_t callbackId, FrameCallback* frameCallback) { - m_frameCallbacks[callbackId] = frameCallback; -} - -void FrameRequestCallbackCollection::cancelFrameCallback(uint32_t callbackId) { - if (m_frameCallbacks.count(callbackId) == 0) - return; - FrameCallback* callback = m_frameCallbacks[callbackId]; - - // Push this timer to abandoned list to mark this timer is deprecated. - m_abandonedCallbacks.emplace_back(callback); - - m_frameCallbacks.erase(callbackId); -} - -void FrameRequestCallbackCollection::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - for (auto& callback : m_frameCallbacks) { - JS_MarkValue(rt, callback.second->toQuickJS(), mark_func); - } - - // Recycle all abandoned callbacks. - if (!m_abandonedCallbacks.empty()) { - for (auto& callback : m_abandonedCallbacks) { - JS_MarkValue(rt, callback->toQuickJS(), mark_func); - } - // All abandoned timers should be freed at the sweep stage. - m_abandonedCallbacks.clear(); - } -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/frame_request_callback_collection.h b/bridge/bindings/qjs/dom/frame_request_callback_collection.h deleted file mode 100644 index 5839069758..0000000000 --- a/bridge/bindings/qjs/dom/frame_request_callback_collection.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ -#define KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ - -#include "bindings/qjs/executing_context.h" - -namespace kraken::binding::qjs { - -// |FrameCallback| is an interface type which generalizes callbacks which are -// invoked when a script-based animation needs to be resampled. -class FrameCallback : public GarbageCollected { - public: - static JSClassID classId; - - FrameCallback(JSValue callback); - - void fire(double highResTimeStamp); - - [[nodiscard]] FORCE_INLINE const char* getHumanReadableName() const override { return "FrameCallback"; } - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; - void dispose() const override; - - private: - JSValue m_callback{JS_NULL}; - int32_t m_callbackId{-1}; -}; - -class FrameRequestCallbackCollection final { - public: - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func); - void registerFrameCallback(uint32_t callbackId, FrameCallback* frameCallback); - void cancelFrameCallback(uint32_t callbackId); - - private: - std::unordered_map m_frameCallbacks; - std::vector m_abandonedCallbacks; -}; - -} // namespace kraken::binding::qjs - -class frame_request_callback_collection {}; - -#endif // KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ diff --git a/bridge/bindings/qjs/dom/node.cc b/bridge/bindings/qjs/dom/node.cc deleted file mode 100644 index 883d88e667..0000000000 --- a/bridge/bindings/qjs/dom/node.cc +++ /dev/null @@ -1,576 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "node.h" -#include "bindings/qjs/qjs_patch.h" -#include "comment_node.h" -#include "document.h" -#include "document_fragment.h" -#include "element.h" -#include "kraken_bridge.h" -#include "text_node.h" - -namespace kraken::binding::qjs { - -void bindNode(ExecutionContext* context) { - auto* constructor = Node::instance(context); - context->defineGlobalProperty("Node", constructor->jsObject); -} - -JSValue Node::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - return JS_ThrowTypeError(ctx, "Illegal constructor"); -} - -JSClassID Node::classId() { - assert_m(false, "classId is not implemented"); - return 0; -} - -JSClassID Node::classId(JSValue& value) { - JSClassID classId = JSValueGetClassId(value); - if (classId == Element::classId() || classId == Document::classId() || classId == TextNode::classId() || classId == Comment::classId() || classId == DocumentFragment::classId()) { - return classId; - } - - return 0; -} - -JSValue Node::cloneNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto selfInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - - JSValue deepValue; - if (argc < 1) { - deepValue = JS_NewBool(ctx, false); - } else { - deepValue = argv[0]; - } - - if (!JS_IsBool(deepValue)) { - return JS_ThrowTypeError(ctx, "Failed to cloneNode: deep should be a Boolean."); - } - bool deep = JS_ToBool(ctx, deepValue); - - if (selfInstance->nodeType == NodeType::ELEMENT_NODE) { - JSValue newElement = copyNodeValue(ctx, selfInstance); - auto newElementInstance = static_cast(JS_GetOpaque(newElement, Node::classId(newElement))); - - if (deep) { - traverseCloneNode(ctx, selfInstance, newElementInstance); - } - return newElementInstance->jsObject; - } else if (selfInstance->nodeType == NodeType::TEXT_NODE) { - auto textNode = static_cast(selfInstance); - JSValue newTextNode = copyNodeValue(ctx, static_cast(textNode)); - return newTextNode; - } else if (selfInstance->nodeType == NodeType::DOCUMENT_FRAGMENT_NODE) { - JSValue newFragment = JS_CallConstructor(ctx, DocumentFragment::instance(selfInstance->m_context)->jsObject, 0, nullptr); - auto* newFragmentInstance = static_cast(JS_GetOpaque(newFragment, Node::classId(newFragment))); - - if (deep) { - traverseCloneNode(ctx, selfInstance, newFragmentInstance); - } - - return newFragment; - } - return JS_NULL; -} - -JSValue Node::appendChild(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc != 1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': first argument is required."); - } - - auto selfInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - if (selfInstance == nullptr) - return JS_ThrowTypeError(ctx, "this object is not a instance of Node."); - JSValue nodeValue = argv[0]; - - if (!JS_IsObject(nodeValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': first arguments should be an Node type."); - } - - auto* nodeInstance = static_cast(JS_GetOpaque(nodeValue, Node::classId(nodeValue))); - - if (nodeInstance == nullptr || nodeInstance->document() != selfInstance->document()) { - return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': first arguments should be an Node type."); - } - - if (nodeInstance == selfInstance) { - return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': The new child element contains the parent."); - } - - if (nodeInstance->hasNodeFlag(NodeInstance::NodeFlag::IsDocumentFragment)) { - size_t len = arrayGetLength(ctx, nodeInstance->childNodes); - for (int i = 0; i < len; i++) { - JSValue n = JS_GetPropertyUint32(ctx, nodeInstance->childNodes, i); - auto* node = static_cast(JS_GetOpaque(n, Node::classId(n))); - selfInstance->internalAppendChild(node); - JS_FreeValue(ctx, n); - } - - JS_SetPropertyStr(ctx, nodeInstance->childNodes, "length", JS_NewUint32(ctx, 0)); - } else { - selfInstance->ensureDetached(nodeInstance); - selfInstance->internalAppendChild(nodeInstance); - } - - return JS_DupValue(ctx, nodeInstance->jsObject); -} -JSValue Node::remove(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto selfInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - selfInstance->internalRemove(); - return JS_UNDEFINED; -} -JSValue Node::removeChild(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'removeChild' on 'Node': 1 arguments required"); - } - - JSValue nodeValue = argv[0]; - - if (!JS_IsObject(nodeValue)) { - return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'removeChild' on 'Node': 1st arguments is not object"); - } - - auto selfInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - auto nodeInstance = static_cast(JS_GetOpaque(nodeValue, Node::classId(nodeValue))); - - if (nodeInstance == nullptr || nodeInstance->document() != selfInstance->document()) { - return JS_ThrowTypeError(ctx, "Failed to execute 'removeChild' on 'Node': 1st arguments is not a Node object."); - } - - auto removedNode = selfInstance->internalRemoveChild(nodeInstance); - return JS_DupValue(ctx, removedNode->jsObject); -} - -JSValue Node::insertBefore(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 2) { - return JS_ThrowTypeError(ctx, "Failed to execute 'insertBefore' on 'Node': 2 arguments is required."); - } - - JSValue nodeValue = argv[0]; - JSValue referenceNodeValue = argv[1]; - - if (!JS_IsObject(nodeValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'insertBefore' on 'Node': the node element is not object."); - } - - NodeInstance* referenceInstance = nullptr; - - if (JS_IsObject(referenceNodeValue)) { - referenceInstance = static_cast(JS_GetOpaque(referenceNodeValue, Node::classId(referenceNodeValue))); - } else if (!JS_IsNull(referenceNodeValue)) { - return JS_ThrowTypeError(ctx, "TypeError: Failed to execute 'insertBefore' on 'Node': parameter 2 is not of type 'Node'"); - } - - auto selfInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - auto nodeInstance = static_cast(JS_GetOpaque(nodeValue, Node::classId(nodeValue))); - - if (nodeInstance == nullptr || nodeInstance->document() != selfInstance->document()) { - return JS_ThrowTypeError(ctx, "Failed to execute 'insertBefore' on 'Node': parameter 1 is not of type 'Node'"); - } - - if (nodeInstance->hasNodeFlag(NodeInstance::NodeFlag::IsDocumentFragment)) { - size_t len = arrayGetLength(ctx, nodeInstance->childNodes); - for (int i = 0; i < len; i++) { - JSValue n = JS_GetPropertyUint32(ctx, nodeInstance->childNodes, i); - auto* node = static_cast(JS_GetOpaque(n, Node::classId(n))); - selfInstance->internalInsertBefore(node, referenceInstance); - JS_FreeValue(ctx, n); - } - - // Clear fragment childNodes reference. - JS_SetPropertyStr(ctx, nodeInstance->childNodes, "length", JS_NewUint32(ctx, 0)); - } else { - selfInstance->ensureDetached(nodeInstance); - selfInstance->internalInsertBefore(nodeInstance, referenceInstance); - } - - return JS_NULL; -} - -JSValue Node::replaceChild(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 2) { - return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'replaceChild' on 'Node': 2 arguments required"); - } - - JSValue newChildValue = argv[0]; - JSValue oldChildValue = argv[1]; - - if (!JS_IsObject(newChildValue)) { - return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'replaceChild' on 'Node': 1 arguments is not object"); - } - - if (!JS_IsObject(oldChildValue)) { - return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'replaceChild' on 'Node': 2 arguments is not object."); - } - - auto selfInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - auto newChildInstance = static_cast(JS_GetOpaque(newChildValue, Node::classId(newChildValue))); - auto oldChildInstance = static_cast(JS_GetOpaque(oldChildValue, Node::classId(oldChildValue))); - - if (oldChildInstance == nullptr || JS_VALUE_GET_PTR(oldChildInstance->parentNode) != JS_VALUE_GET_PTR(selfInstance->jsObject) || oldChildInstance->document() != selfInstance->document()) { - return JS_ThrowTypeError(ctx, "Failed to execute 'replaceChild' on 'Node': The node to be replaced is not a child of this node."); - } - - if (newChildInstance == nullptr || newChildInstance->document() != selfInstance->document()) { - return JS_ThrowTypeError(ctx, "Failed to execute 'replaceChild' on 'Node': The new node is not a type of node."); - } - - if (newChildInstance->hasNodeFlag(NodeInstance::NodeFlag::IsDocumentFragment)) { - size_t len = arrayGetLength(ctx, newChildInstance->childNodes); - for (int i = 0; i < len; i++) { - JSValue n = JS_GetPropertyUint32(ctx, newChildInstance->childNodes, i); - auto* node = static_cast(JS_GetOpaque(n, Node::classId(n))); - selfInstance->internalInsertBefore(node, oldChildInstance); - JS_FreeValue(ctx, n); - } - selfInstance->internalRemoveChild(oldChildInstance); - // Clear fragment childNodes reference. - JS_SetPropertyStr(ctx, newChildInstance->childNodes, "length", JS_NewUint32(ctx, 0)); - } else { - selfInstance->ensureDetached(newChildInstance); - selfInstance->internalReplaceChild(newChildInstance, oldChildInstance); - } - return JS_DupValue(ctx, oldChildInstance->jsObject); -} - -void Node::traverseCloneNode(JSContext* ctx, NodeInstance* baseNode, NodeInstance* targetNode) { - int32_t len = arrayGetLength(ctx, baseNode->childNodes); - for (int i = 0; i < len; i++) { - JSValue n = JS_GetPropertyUint32(ctx, baseNode->childNodes, i); - auto* node = static_cast(JS_GetOpaque(n, Node::classId(n))); - JSValue newNode = copyNodeValue(ctx, node); - auto newNodeInstance = static_cast(JS_GetOpaque(newNode, Node::classId(newNode))); - targetNode->ensureDetached(newNodeInstance); - targetNode->internalAppendChild(newNodeInstance); - // element node needs recursive child nodes. - if (node->nodeType == NodeType::ELEMENT_NODE) { - traverseCloneNode(ctx, node, newNodeInstance); - } - JS_FreeValue(ctx, newNode); - JS_FreeValue(ctx, n); - } -} - -JSValue Node::copyNodeValue(JSContext* ctx, NodeInstance* node) { - if (node->nodeType == NodeType::ELEMENT_NODE) { - auto* element = reinterpret_cast(node); - - /* createElement */ - std::string tagName = element->getRegisteredTagName(); - JSValue tagNameValue = JS_NewString(element->m_ctx, tagName.c_str()); - JSValue arguments[] = {tagNameValue}; - JSValue newElementValue = JS_CallConstructor(element->context()->ctx(), Element::instance(element->context())->jsObject, 1, arguments); - JS_FreeValue(ctx, tagNameValue); - - auto* newElement = static_cast(JS_GetOpaque(newElementValue, Node::classId(newElementValue))); - - /* copy attributes */ - newElement->m_attributes->copyWith(element->m_attributes); - - /* copy style */ - newElement->m_style->copyWith(element->m_style); - - /* copy properties */ - ElementInstance::copyNodeProperties(newElement, element); - - std::string newNodeEventTargetId = std::to_string(newElement->m_eventTargetId); - std::unique_ptr args_01 = stringToNativeString(newNodeEventTargetId); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::cloneNode, *args_01, nullptr); - - return newElement->jsObject; - } else if (node->nodeType == TEXT_NODE) { - auto* textNode = reinterpret_cast(node); - JSValue textContent = textNode->internalGetTextContent(); - JSValue arguments[] = {textContent}; - JSValue result = JS_CallConstructor(ctx, TextNode::instance(textNode->m_context)->jsObject, 1, arguments); - JS_FreeValue(ctx, textContent); - return result; - } - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(Node, isConnected)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - return JS_NewBool(ctx, nodeInstance->isConnected()); -} - -IMPL_PROPERTY_GETTER(Node, ownerDocument)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - return JS_DupValue(ctx, nodeInstance->m_document->jsObject); -} - -IMPL_PROPERTY_GETTER(Node, firstChild)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - auto* instance = nodeInstance->firstChild(); - return instance != nullptr ? instance->jsObject : JS_NULL; -} - -IMPL_PROPERTY_GETTER(Node, lastChild)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - auto* instance = nodeInstance->lastChild(); - return instance != nullptr ? instance->jsObject : JS_NULL; -} - -IMPL_PROPERTY_GETTER(Node, parentNode)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - return JS_DupValue(ctx, nodeInstance->parentNode); -} - -IMPL_PROPERTY_GETTER(Node, previousSibling)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - auto* instance = nodeInstance->previousSibling(); - return instance != nullptr ? instance->jsObject : JS_NULL; -} - -IMPL_PROPERTY_GETTER(Node, nextSibling)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - auto* instance = nodeInstance->nextSibling(); - return instance != nullptr ? instance->jsObject : JS_NULL; -} - -IMPL_PROPERTY_GETTER(Node, nodeType)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - return JS_NewUint32(ctx, nodeInstance->nodeType); -} - -IMPL_PROPERTY_GETTER(Node, textContent)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - return nodeInstance->internalGetTextContent(); -} -IMPL_PROPERTY_SETTER(Node, textContent)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* nodeInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); - nodeInstance->internalSetTextContent(argv[0]); - return JS_NULL; -} - -bool NodeInstance::isConnected() { - bool _isConnected = this == document(); - auto parent = static_cast(JS_GetOpaque(parentNode, Node::classId(parentNode))); - - while (parent != nullptr && !_isConnected) { - _isConnected = parent == document(); - JSValue parentParentNode = parent->parentNode; - parent = static_cast(JS_GetOpaque(parentParentNode, Node::classId(parentParentNode))); - } - - return _isConnected; -} -DocumentInstance* NodeInstance::ownerDocument() { - if (nodeType == NodeType::DOCUMENT_NODE) { - return nullptr; - } - - return document(); -} -NodeInstance* NodeInstance::firstChild() { - int32_t len = arrayGetLength(m_ctx, childNodes); - if (len == 0) { - return nullptr; - } - JSValue result = JS_GetPropertyUint32(m_ctx, childNodes, 0); - return static_cast(JS_GetOpaque(result, Node::classId(result))); -} -NodeInstance* NodeInstance::lastChild() { - int32_t len = arrayGetLength(m_ctx, childNodes); - if (len == 0) { - return nullptr; - } - JSValue result = JS_GetPropertyUint32(m_ctx, childNodes, len - 1); - return static_cast(JS_GetOpaque(result, Node::classId(result))); -} -NodeInstance* NodeInstance::previousSibling() { - if (JS_IsNull(parentNode)) - return nullptr; - - auto* parent = static_cast(JS_GetOpaque(parentNode, Node::classId(parentNode))); - auto parentChildNodes = parent->childNodes; - int32_t idx = arrayFindIdx(m_ctx, parentChildNodes, jsObject); - int32_t parentChildNodeLen = arrayGetLength(m_ctx, parentChildNodes); - - if (idx - 1 < parentChildNodeLen) { - JSValue result = JS_GetPropertyUint32(m_ctx, parentChildNodes, idx - 1); - return static_cast(JS_GetOpaque(result, Node::classId(result))); - } - - return nullptr; -} -NodeInstance* NodeInstance::nextSibling() { - if (JS_IsNull(parentNode)) - return nullptr; - auto* parent = static_cast(JS_GetOpaque(parentNode, Node::classId(parentNode))); - auto parentChildNodes = parent->childNodes; - int32_t idx = arrayFindIdx(m_ctx, parentChildNodes, jsObject); - int32_t parentChildNodeLen = arrayGetLength(m_ctx, parentChildNodes); - - if (idx + 1 < parentChildNodeLen) { - JSValue result = JS_GetPropertyUint32(m_ctx, parentChildNodes, idx + 1); - return static_cast(JS_GetOpaque(result, Node::classId(result))); - } - - return nullptr; -} -void NodeInstance::internalAppendChild(NodeInstance* node) { - arrayPushValue(m_ctx, childNodes, node->jsObject); - node->setParentNode(this); - - node->_notifyNodeInsert(this); - - std::string nodeEventTargetId = std::to_string(node->m_eventTargetId); - std::string position = std::string("beforeend"); - - std::unique_ptr args_01 = stringToNativeString(nodeEventTargetId); - std::unique_ptr args_02 = stringToNativeString(position); - - m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); -} -void NodeInstance::internalRemove() { - if (JS_IsNull(parentNode)) - return; - auto* parent = static_cast(JS_GetOpaque(parentNode, Node::classId(parentNode))); - parent->internalRemoveChild(this); -} -void NodeInstance::internalClearChild() { - int32_t len = arrayGetLength(m_ctx, childNodes); - - for (int i = 0; i < len; i++) { - JSValue v = JS_GetPropertyUint32(m_ctx, childNodes, i); - auto* node = static_cast(JS_GetOpaque(v, Node::classId(v))); - node->removeParentNode(); - node->_notifyNodeRemoved(this); - node->m_context->uiCommandBuffer()->addCommand(node->m_eventTargetId, UICommand::removeNode, nullptr); - JS_FreeValue(m_ctx, v); - } - - JS_SetPropertyStr(m_ctx, childNodes, "length", JS_NewUint32(m_ctx, 0)); -} -NodeInstance* NodeInstance::internalRemoveChild(NodeInstance* node) { - int32_t idx = arrayFindIdx(m_ctx, childNodes, node->jsObject); - - if (idx != -1) { - arraySpliceValue(m_ctx, childNodes, idx, 1); - node->removeParentNode(); - node->_notifyNodeRemoved(this); - node->m_context->uiCommandBuffer()->addCommand(node->m_eventTargetId, UICommand::removeNode, nullptr); - } - - return node; -} -JSValue NodeInstance::internalInsertBefore(NodeInstance* node, NodeInstance* referenceNode) { - if (referenceNode == nullptr) { - internalAppendChild(node); - } else { - if (JS_VALUE_GET_PTR(referenceNode->parentNode) != JS_VALUE_GET_PTR(jsObject)) { - return JS_ThrowTypeError(m_ctx, "Uncaught TypeError: Failed to execute 'insertBefore' on 'Node': reference node is not a child of this node."); - } - - auto parentNodeValue = referenceNode->parentNode; - auto* parent = static_cast(JS_GetOpaque(parentNodeValue, Node::classId(parentNodeValue))); - if (parent != nullptr) { - JSValue parentChildNodes = parent->childNodes; - int32_t idx = arrayFindIdx(m_ctx, parentChildNodes, referenceNode->jsObject); - - if (idx == -1) { - return JS_ThrowTypeError(m_ctx, "Failed to execute 'insertBefore' on 'Node': reference node is not a child of this node."); - } - - arrayInsert(m_ctx, parentChildNodes, idx, node->jsObject); - node->setParentNode(parent); - node->_notifyNodeInsert(parent); - - std::string nodeEventTargetId = std::to_string(node->m_eventTargetId); - std::string position = std::string("beforebegin"); - - std::unique_ptr args_01 = stringToNativeString(nodeEventTargetId); - std::unique_ptr args_02 = stringToNativeString(position); - - m_context->uiCommandBuffer()->addCommand(referenceNode->m_eventTargetId, UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); - } - } - - return JS_NULL; -} -JSValue NodeInstance::internalGetTextContent() { - return JS_NULL; -} -void NodeInstance::internalSetTextContent(JSValue content) {} -JSValue NodeInstance::internalReplaceChild(NodeInstance* newChild, NodeInstance* oldChild) { - assert_m(JS_IsNull(newChild->parentNode), "ReplaceChild Error: newChild was not detached."); - oldChild->removeParentNode(); - - int32_t childIndex = arrayFindIdx(m_ctx, childNodes, oldChild->jsObject); - if (childIndex == -1) { - return JS_ThrowTypeError(m_ctx, "Failed to execute 'replaceChild' on 'Node': old child is not exist on childNodes."); - } - - newChild->setParentNode(this); - - arraySpliceValue(m_ctx, childNodes, childIndex, 1, newChild->jsObject); - - oldChild->_notifyNodeRemoved(this); - newChild->_notifyNodeInsert(this); - - std::string newChildEventTargetId = std::to_string(newChild->m_eventTargetId); - std::string position = std::string("afterend"); - - std::unique_ptr args_01 = stringToNativeString(newChildEventTargetId); - std::unique_ptr args_02 = stringToNativeString(position); - - m_context->uiCommandBuffer()->addCommand(oldChild->m_eventTargetId, UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); - - m_context->uiCommandBuffer()->addCommand(oldChild->m_eventTargetId, UICommand::removeNode, nullptr); - - return oldChild->jsObject; -} - -void NodeInstance::setParentNode(NodeInstance* parent) { - if (!JS_IsNull(parentNode)) { - JS_FreeValue(m_ctx, parentNode); - } - - parentNode = JS_DupValue(m_ctx, parent->jsObject); -} - -void NodeInstance::removeParentNode() { - if (!JS_IsNull(parentNode)) { - JS_FreeValue(m_ctx, parentNode); - } - - parentNode = JS_NULL; -} - -NodeInstance::~NodeInstance() {} -void NodeInstance::refer() { - JS_DupValue(m_ctx, jsObject); - list_add_tail(&nodeLink.link, &m_context->node_job_list); -} -void NodeInstance::unrefer() { - list_del(&nodeLink.link); - JS_FreeValue(m_ctx, jsObject); -} -void NodeInstance::_notifyNodeRemoved(NodeInstance* node) {} -void NodeInstance::_notifyNodeInsert(NodeInstance* node) {} -void NodeInstance::ensureDetached(NodeInstance* node) { - auto* nodeParent = static_cast(JS_GetOpaque(node->parentNode, Node::classId(node->parentNode))); - - if (nodeParent != nullptr) { - int32_t idx = arrayFindIdx(m_ctx, nodeParent->childNodes, node->jsObject); - if (idx != -1) { - node->_notifyNodeRemoved(nodeParent); - arraySpliceValue(m_ctx, nodeParent->childNodes, idx, 1); - node->removeParentNode(); - } - } -} - -void NodeInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - EventTargetInstance::trace(rt, val, mark_func); - - // Should check object is already inited before gc mark. - if (JS_IsObject(parentNode)) - JS_MarkValue(rt, parentNode, mark_func); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/node.h b/bridge/bindings/qjs/dom/node.h deleted file mode 100644 index 32e9d26337..0000000000 --- a/bridge/bindings/qjs/dom/node.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_NODE_H -#define KRAKENBRIDGE_NODE_H - -#include -#include - -#include "event_target.h" - -namespace kraken::binding::qjs { - -void bindNode(ExecutionContext* context); - -enum NodeType { ELEMENT_NODE = 1, TEXT_NODE = 3, COMMENT_NODE = 8, DOCUMENT_NODE = 9, DOCUMENT_TYPE_NODE = 10, DOCUMENT_FRAGMENT_NODE = 11 }; - -class NodeInstance; -class ElementInstance; -class DocumentInstance; -class TextNodeInstance; - -class Node : public EventTarget { - public: - Node() = delete; - Node(ExecutionContext* context, const std::string& className) : EventTarget(context, className.c_str()) { JS_SetPrototype(m_ctx, m_prototypeObject, EventTarget::instance(m_context)->prototype()); } - Node(ExecutionContext* context) : EventTarget(context, "Node") { JS_SetPrototype(m_ctx, m_prototypeObject, EventTarget::instance(m_context)->prototype()); } - - OBJECT_INSTANCE(Node); - - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - static JSClassID classId(); - - static JSClassID classId(JSValue& value); - - static JSValue cloneNode(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue appendChild(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue remove(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue removeChild(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue insertBefore(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue replaceChild(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - private: - DEFINE_PROTOTYPE_PROPERTY(textContent); - - DEFINE_PROTOTYPE_READONLY_PROPERTY(isConnected); - DEFINE_PROTOTYPE_READONLY_PROPERTY(ownerDocument); - DEFINE_PROTOTYPE_READONLY_PROPERTY(firstChild); - DEFINE_PROTOTYPE_READONLY_PROPERTY(lastChild); - DEFINE_PROTOTYPE_READONLY_PROPERTY(parentNode); - DEFINE_PROTOTYPE_READONLY_PROPERTY(previousSibling); - DEFINE_PROTOTYPE_READONLY_PROPERTY(nextSibling); - DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeType); - - DEFINE_PROTOTYPE_FUNCTION(cloneNode, 1); - DEFINE_PROTOTYPE_FUNCTION(appendChild, 1); - DEFINE_PROTOTYPE_FUNCTION(remove, 0); - DEFINE_PROTOTYPE_FUNCTION(removeChild, 1); - DEFINE_PROTOTYPE_FUNCTION(insertBefore, 2); - DEFINE_PROTOTYPE_FUNCTION(replaceChild, 2); - - static void traverseCloneNode(JSContext* ctx, NodeInstance* baseNode, NodeInstance* targetNode); - static JSValue copyNodeValue(JSContext* ctx, NodeInstance* node); - friend ElementInstance; - friend TextNodeInstance; -}; - -struct NodeJob { - NodeInstance* nodeInstance; - list_head link; -}; - -class NodeInstance : public EventTargetInstance { - public: - enum class NodeFlag : uint32_t { IsDocumentFragment = 1 << 0, IsTemplateElement = 1 << 1 }; - mutable std::set m_nodeFlags; - bool hasNodeFlag(NodeFlag flag) const { return m_nodeFlags.size() != 0 && m_nodeFlags.find(flag) != m_nodeFlags.end(); } - void setNodeFlag(NodeFlag flag) const { m_nodeFlags.insert(flag); } - void removeNodeFlag(NodeFlag flag) const { m_nodeFlags.erase(flag); } - - NodeInstance() = delete; - explicit NodeInstance(Node* node, NodeType nodeType, JSClassID classId, std::string name) - : EventTargetInstance(node, classId, std::move(name)), m_document(m_context->document()), nodeType(nodeType) {} - explicit NodeInstance(Node* node, NodeType nodeType, JSClassID classId, JSClassExoticMethods& exoticMethods, std::string name) - : EventTargetInstance(node, classId, exoticMethods, name), m_document(m_context->document()), nodeType(nodeType) {} - ~NodeInstance(); - bool isConnected(); - DocumentInstance* ownerDocument(); - NodeInstance* firstChild(); - NodeInstance* lastChild(); - NodeInstance* previousSibling(); - NodeInstance* nextSibling(); - void internalAppendChild(NodeInstance* node); - void internalRemove(); - void internalClearChild(); - NodeInstance* internalRemoveChild(NodeInstance* node); - JSValue internalInsertBefore(NodeInstance* node, NodeInstance* referenceNode); - virtual JSValue internalGetTextContent(); - virtual void internalSetTextContent(JSValue content); - JSValue internalReplaceChild(NodeInstance* newChild, NodeInstance* oldChild); - - void setParentNode(NodeInstance* parent); - void removeParentNode(); - NodeType nodeType; - JSValue parentNode{JS_NULL}; - JSValue childNodes{JS_NewArray(m_ctx)}; - - NodeJob nodeLink{this}; - - void refer(); - void unrefer(); - inline DocumentInstance* document() { return m_document; } - - virtual void _notifyNodeRemoved(NodeInstance* node); - virtual void _notifyNodeInsert(NodeInstance* node); - - protected: - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; - - private: - DocumentInstance* m_document{nullptr}; - ObjectProperty m_childNodes{m_context, jsObject, "childNodes", childNodes}; - void ensureDetached(NodeInstance* node); - friend DocumentInstance; - friend Node; - friend ElementInstance; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_NODE_H diff --git a/bridge/bindings/qjs/dom/script_animation_controller.cc b/bridge/bindings/qjs/dom/script_animation_controller.cc deleted file mode 100644 index 1e7581d800..0000000000 --- a/bridge/bindings/qjs/dom/script_animation_controller.cc +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "script_animation_controller.h" -#include "dart_methods.h" -#include "frame_request_callback_collection.h" - -#if UNIT_TEST -#include "kraken_test_env.h" -#endif - -namespace kraken::binding::qjs { - -JSClassID ScriptAnimationController::classId{0}; - -void ScriptAnimationController::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - auto* controller = static_cast(JS_GetOpaque(val, ScriptAnimationController::classId)); - controller->m_frameRequestCallbackCollection.trace(rt, JS_UNDEFINED, mark_func); -} -void ScriptAnimationController::dispose() const {} - -ScriptAnimationController* ScriptAnimationController::initialize(JSContext* ctx, JSClassID* classId) { - return GarbageCollected::initialize(ctx, classId); -} - -static void handleRAFTransientCallback(void* ptr, int32_t contextId, double highResTimeStamp, const char* errmsg) { - auto* frameCallback = static_cast(ptr); - auto* context = static_cast(JS_GetContextOpaque(frameCallback->ctx())); - - if (!context->isValid()) - return; - - if (errmsg != nullptr) { - JSValue exception = JS_ThrowTypeError(frameCallback->ctx(), "%s", errmsg); - context->handleException(&exception); - return; - } - - // Trigger callbacks. - frameCallback->fire(highResTimeStamp); - - context->drainPendingPromiseJobs(); -} - -uint32_t ScriptAnimationController::registerFrameCallback(FrameCallback* frameCallback) { - auto* context = static_cast(JS_GetContextOpaque(m_ctx)); - - uint32_t requestId = getDartMethod()->requestAnimationFrame(frameCallback, context->getContextId(), handleRAFTransientCallback); - - // Register frame callback to collection. - m_frameRequestCallbackCollection.registerFrameCallback(requestId, frameCallback); - - return requestId; -} -void ScriptAnimationController::cancelFrameCallback(uint32_t callbackId) { - auto* context = static_cast(JS_GetContextOpaque(m_ctx)); - - getDartMethod()->cancelAnimationFrame(context->getContextId(), callbackId); - - m_frameRequestCallbackCollection.cancelFrameCallback(callbackId); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/script_animation_controller.h b/bridge/bindings/qjs/dom/script_animation_controller.h deleted file mode 100644 index 5939bea7bf..0000000000 --- a/bridge/bindings/qjs/dom/script_animation_controller.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ -#define KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ - -#include "bindings/qjs/garbage_collected.h" -#include "frame_request_callback_collection.h" - -namespace kraken::binding::qjs { - -class ScriptAnimationController : public GarbageCollected { - public: - static JSClassID classId; - - ScriptAnimationController* initialize(JSContext* ctx, JSClassID* classId) override; - - // Animation frame callbacks are used for requestAnimationFrame(). - uint32_t registerFrameCallback(FrameCallback* frameCallback); - void cancelFrameCallback(uint32_t callbackId); - - [[nodiscard]] FORCE_INLINE const char* getHumanReadableName() const override { return "ScriptAnimationController"; } - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; - void dispose() const override; - - private: - FrameRequestCallbackCollection m_frameRequestCallbackCollection; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ diff --git a/bridge/bindings/qjs/dom/style_declaration.cc b/bridge/bindings/qjs/dom/style_declaration.cc index 0a1c58377c..e69de29bb2 100644 --- a/bridge/bindings/qjs/dom/style_declaration.cc +++ b/bridge/bindings/qjs/dom/style_declaration.cc @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "style_declaration.h" -#include "bindings/qjs/dom/css_property_list.h" -#include "event_target.h" -#include "kraken_bridge.h" - -namespace kraken::binding::qjs { - -std::once_flag kinitCSSStyleDeclarationFlag; - -void bindCSSStyleDeclaration(ExecutionContext* context) { - auto style = CSSStyleDeclaration::instance(context); - context->defineGlobalProperty("CSSStyleDeclaration", style->jsObject); -} - -static std::string parseJavaScriptCSSPropertyName(std::string& propertyName) { - static std::unordered_map propertyCache{}; - - if (propertyCache.count(propertyName) > 0) { - return propertyCache[propertyName]; - } - - std::vector buffer(propertyName.size() + 1); - - size_t hyphen = 0; - for (size_t i = 0; i < propertyName.size(); ++i) { - char c = propertyName[i + hyphen]; - if (!c) - break; - if (c == '-') { - hyphen++; - buffer[i] = toASCIIUpper(propertyName[i + hyphen]); - } else { - buffer[i] = c; - } - } - - buffer.emplace_back('\0'); - - std::string result = std::string(buffer.data()); - - propertyCache[propertyName] = result; - return result; -} - -JSValue CSSStyleDeclaration::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - if (argc != 1) { - return JS_ThrowTypeError(ctx, "Illegal constructor"); - } - - JSValue eventTargetValue = argv[0]; - - auto eventTargetInstance = static_cast(JS_GetOpaque(eventTargetValue, EventTarget::classId(eventTargetValue))); - auto style = new StyleDeclarationInstance(this, eventTargetInstance); - return style->jsObject; -} - -JSClassID CSSStyleDeclaration::kCSSStyleDeclarationClassId{0}; - -CSSStyleDeclaration::CSSStyleDeclaration(ExecutionContext* context) : HostClass(context, "CSSStyleDeclaration") { - std::call_once(kinitCSSStyleDeclarationFlag, []() { JS_NewClassID(&kCSSStyleDeclarationClassId); }); -} - -JSValue CSSStyleDeclaration::setProperty(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 2) - return JS_ThrowTypeError(ctx, "Failed to execute 'setProperty' on 'CSSStyleDeclaration': 2 arguments required, but only %d present.", argc); - auto* instance = static_cast(JS_GetOpaque(this_val, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - JSValue propertyNameValue = argv[0]; - JSValue propertyValue = argv[1]; - - const char* cPropertyName = JS_ToCString(ctx, propertyNameValue); - std::string propertyName = std::string(cPropertyName); - - instance->internalSetProperty(propertyName, propertyValue); - - JS_FreeCString(ctx, cPropertyName); - - return JS_UNDEFINED; -} - -JSValue CSSStyleDeclaration::removeProperty(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) - return JS_ThrowTypeError(ctx, "Failed to execute 'removeProperty' on 'CSSStyleDeclaration': 1 arguments required, but only 0 present."); - auto* instance = static_cast(JS_GetOpaque(this_val, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - - JSValue propertyNameValue = argv[0]; - - const char* cPropertyName = JS_ToCString(ctx, propertyNameValue); - std::string propertyName = std::string(cPropertyName); - - instance->internalRemoveProperty(propertyName); - - JS_FreeCString(ctx, cPropertyName); - - return JS_UNDEFINED; -} - -JSValue CSSStyleDeclaration::getPropertyValue(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) - return JS_ThrowTypeError(ctx, "Failed to execute 'getPropertyValue' on 'CSSStyleDeclaration': 1 arguments required, but only 0 present."); - auto* instance = static_cast(JS_GetOpaque(this_val, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - JSValue propertyNameValue = argv[0]; - const char* cPropertyName = JS_ToCString(ctx, propertyNameValue); - std::string propertyName = std::string(cPropertyName); - - JSValue returnValue = instance->internalGetPropertyValue(propertyName); - JS_FreeCString(ctx, cPropertyName); - return returnValue; -} - -StyleDeclarationInstance::StyleDeclarationInstance(CSSStyleDeclaration* cssStyleDeclaration, EventTargetInstance* ownerEventTarget) - : Instance(cssStyleDeclaration, "CSSStyleDeclaration", &m_exoticMethods, CSSStyleDeclaration::kCSSStyleDeclarationClassId, finalize), ownerEventTarget(ownerEventTarget) { - JS_DupValue(m_ctx, ownerEventTarget->jsObject); -} -StyleDeclarationInstance::~StyleDeclarationInstance() {} - -bool StyleDeclarationInstance::internalSetProperty(std::string& name, JSValue value) { - name = parseJavaScriptCSSPropertyName(name); - - properties[name] = jsValueToStdString(m_ctx, value); - - if (ownerEventTarget != nullptr) { - std::unique_ptr args_01 = stringToNativeString(name); - std::unique_ptr args_02 = jsValueToNativeString(m_ctx, value); - m_context->uiCommandBuffer()->addCommand(ownerEventTarget->eventTargetId(), UICommand::setStyle, *args_01, *args_02, nullptr); - } - - return true; -} - -void StyleDeclarationInstance::internalRemoveProperty(std::string& name) { - name = parseJavaScriptCSSPropertyName(name); - - if (properties.count(name) == 0) { - return; - } - - properties.erase(name); - - if (ownerEventTarget != nullptr) { - std::unique_ptr args_01 = stringToNativeString(name); - std::unique_ptr args_02 = jsValueToNativeString(m_ctx, JS_NULL); - m_context->uiCommandBuffer()->addCommand(ownerEventTarget->eventTargetId(), UICommand::setStyle, *args_01, *args_02, nullptr); - } -} - -JSValue StyleDeclarationInstance::internalGetPropertyValue(std::string& name) { - name = parseJavaScriptCSSPropertyName(name); - - if (properties.count(name) > 0) { - return JS_NewString(m_ctx, properties[name].c_str()); - } - - return JS_NewString(m_ctx, ""); -} - -// TODO: add support for annotation CSS styleSheets. -std::string StyleDeclarationInstance::toString() { - if (properties.empty()) - return ""; - - std::string s; - - for (auto& attr : properties) { - s += attr.first + ": " + attr.second + ";"; - } - - s += "\""; - return s; -} - -void StyleDeclarationInstance::copyWith(StyleDeclarationInstance* instance) { - for (auto& attr : instance->properties) { - properties[attr.first] = attr.second; - } -} - -int StyleDeclarationInstance::hasProperty(JSContext* ctx, JSValue obj, JSAtom atom) { - auto* style = static_cast(JS_GetOpaque(obj, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - const char* cname = JS_AtomToCString(ctx, atom); - std::string name = std::string(cname); - - if (cssPropertyList.count(name) > 0) { - JS_FreeCString(ctx, cname); - return true; - } - - bool match = style->properties.count(name) > 0; - JS_FreeCString(ctx, cname); - return match; -} - -int StyleDeclarationInstance::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { - auto* style = static_cast(JS_GetOpaque(receiver, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - const char* cname = JS_AtomToCString(ctx, atom); - std::string name = std::string(cname); - bool success = style->internalSetProperty(name, value); - JS_FreeCString(ctx, cname); - return success; -} - -JSValue StyleDeclarationInstance::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { - auto* styleInstance = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); - JSValue prototype = JS_GetPrototype(ctx, styleInstance->jsObject); - if (JS_HasProperty(ctx, prototype, atom)) { - JSValue ret = JS_GetPropertyInternal(ctx, prototype, atom, styleInstance->jsObject, 0); - JS_FreeValue(ctx, prototype); - return ret; - } - JS_FreeValue(ctx, prototype); - - auto* style = static_cast(JS_GetOpaque(receiver, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - const char* cname = JS_AtomToCString(ctx, atom); - std::string name = std::string(cname); - JSValue result = style->internalGetPropertyValue(name); - JS_FreeCString(ctx, cname); - return result; -} - -JSClassExoticMethods StyleDeclarationInstance::m_exoticMethods{ - nullptr, nullptr, nullptr, nullptr, hasProperty, getProperty, setProperty, -}; - -void StyleDeclarationInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - Instance::trace(rt, val, mark_func); - // We should tel gc style relies on element - JS_MarkValue(rt, ownerEventTarget->jsObject, mark_func); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/style_declaration.h b/bridge/bindings/qjs/dom/style_declaration.h deleted file mode 100644 index f1f5a3fc58..0000000000 --- a/bridge/bindings/qjs/dom/style_declaration.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_STYLE_DECLARATION_H -#define KRAKENBRIDGE_STYLE_DECLARATION_H - -#include "bindings/qjs/host_class.h" - -namespace kraken::binding::qjs { - -class EventTargetInstance; -void bindCSSStyleDeclaration(ExecutionContext* context); - -template -inline bool isASCIILower(CharacterType character) { - return character >= 'a' && character <= 'z'; -} - -template -inline CharacterType toASCIIUpper(CharacterType character) { - return character & ~(isASCIILower(character) << 5); -} - -class CSSStyleDeclaration : public HostClass { - public: - OBJECT_INSTANCE(CSSStyleDeclaration); - - static JSClassID kCSSStyleDeclarationClassId; - - CSSStyleDeclaration() = delete; - ~CSSStyleDeclaration(){}; - explicit CSSStyleDeclaration(ExecutionContext* context); - - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - static JSValue setProperty(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue removeProperty(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getPropertyValue(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - protected: - DEFINE_PROTOTYPE_FUNCTION(setProperty, 2); - DEFINE_PROTOTYPE_FUNCTION(getPropertyValue, 2); - DEFINE_PROTOTYPE_FUNCTION(removeProperty, 2); -}; - -class StyleDeclarationInstance : public Instance { - public: - StyleDeclarationInstance() = delete; - explicit StyleDeclarationInstance(CSSStyleDeclaration* cssStyleDeclaration, EventTargetInstance* ownerEventTarget); - ~StyleDeclarationInstance(); - bool internalSetProperty(std::string& name, JSValue value); - void internalRemoveProperty(std::string& name); - JSValue internalGetPropertyValue(std::string& name); - std::string toString(); - void copyWith(StyleDeclarationInstance* instance); - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; - - const EventTargetInstance* ownerEventTarget; - - private: - static int hasProperty(JSContext* ctx, JSValueConst obj, JSAtom atom); - static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); - - static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); - - static void finalize(JSRuntime* rt, JSValue val) { - auto* instance = static_cast(JS_GetOpaque(val, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - delete instance; - } - - static JSClassExoticMethods m_exoticMethods; - - std::unordered_map properties; - friend EventTargetInstance; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_STYLE_DECLARATION_H diff --git a/bridge/bindings/qjs/dom/text_node.cc b/bridge/bindings/qjs/dom/text_node.cc deleted file mode 100644 index 37be3f38c4..0000000000 --- a/bridge/bindings/qjs/dom/text_node.cc +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "text_node.h" -#include "document.h" -#include "kraken_bridge.h" - -namespace kraken::binding::qjs { - -std::once_flag kTextNodeInitFlag; - -void bindTextNode(ExecutionContext* context) { - auto* constructor = TextNode::instance(context); - context->defineGlobalProperty("Text", constructor->jsObject); -} - -JSClassID TextNode::kTextNodeClassId{0}; - -TextNode::TextNode(ExecutionContext* context) : Node(context, "TextNode") { - std::call_once(kTextNodeInitFlag, []() { JS_NewClassID(&kTextNodeClassId); }); - JS_SetPrototype(m_ctx, m_prototypeObject, Node::instance(m_context)->prototype()); -} - -JSValue TextNode::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - JSValue textContent = JS_NULL; - if (argc == 1) { - textContent = argv[0]; - } - - return (new TextNodeInstance(this, textContent))->jsObject; -} - -JSClassID TextNode::classId() { - return kTextNodeClassId; -} - -IMPL_PROPERTY_GETTER(TextNode, data)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* textNode = static_cast(JS_GetOpaque(this_val, TextNode::classId())); - return JS_NewString(ctx, textNode->m_data.c_str()); -} -IMPL_PROPERTY_SETTER(TextNode, data)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* textNode = static_cast(JS_GetOpaque(this_val, TextNode::classId())); - textNode->internalSetTextContent(argv[0]); - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(TextNode, nodeValue)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* textNode = static_cast(JS_GetOpaque(this_val, TextNode::classId())); - return JS_NewString(ctx, textNode->m_data.c_str()); -} -IMPL_PROPERTY_SETTER(TextNode, nodeValue)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* textNode = static_cast(JS_GetOpaque(this_val, TextNode::classId())); - textNode->internalSetTextContent(argv[0]); - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(TextNode, nodeName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NewString(ctx, "#text"); -} - -TextNodeInstance::TextNodeInstance(TextNode* textNode, JSValue text) : NodeInstance(textNode, NodeType::TEXT_NODE, TextNode::classId(), "TextNode") { - m_data = jsValueToStdString(m_ctx, text); - std::unique_ptr args_01 = stringToNativeString(m_data); - m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createTextNode, *args_01, nativeEventTarget); -} - -TextNodeInstance::~TextNodeInstance() {} - -std::string TextNodeInstance::toString() { - return m_data; -} - -JSValue TextNodeInstance::internalGetTextContent() { - return JS_NewString(m_ctx, m_data.c_str()); -} -void TextNodeInstance::internalSetTextContent(JSValue content) { - m_data = jsValueToStdString(m_ctx, content); - - std::string key = "data"; - std::unique_ptr args_01 = stringToNativeString(key); - std::unique_ptr args_02 = jsValueToNativeString(m_ctx, content); - m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::setAttribute, *args_01, *args_02, nullptr); -} -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/text_node.h b/bridge/bindings/qjs/dom/text_node.h deleted file mode 100644 index d9c293f719..0000000000 --- a/bridge/bindings/qjs/dom/text_node.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_TEXT_NODE_H -#define KRAKENBRIDGE_TEXT_NODE_H - -#include "node.h" - -namespace kraken::binding::qjs { - -class TextNodeInstance; - -void bindTextNode(ExecutionContext* context); - -class TextNode : public Node { - public: - static JSClassID kTextNodeClassId; - static JSClassID classId(); - TextNode() = delete; - explicit TextNode(ExecutionContext* context); - - OBJECT_INSTANCE(TextNode); - - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - private: - DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); - - DEFINE_PROTOTYPE_PROPERTY(data); - DEFINE_PROTOTYPE_PROPERTY(nodeValue); - friend TextNodeInstance; -}; - -class TextNodeInstance : public NodeInstance { - public: - TextNodeInstance() = delete; - explicit TextNodeInstance(TextNode* textNode, JSValue textData); - ~TextNodeInstance(); - - std::string toString(); - - private: - JSValue internalGetTextContent() override; - void internalSetTextContent(JSValue content) override; - friend TextNode; - friend Node; - - std::string m_data; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_TEXT_NODE_H diff --git a/bridge/bindings/qjs/exception_message.cc b/bridge/bindings/qjs/exception_message.cc new file mode 100644 index 0000000000..195b312b6c --- /dev/null +++ b/bridge/bindings/qjs/exception_message.cc @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "exception_message.h" +#include + +namespace kraken { + +std::string ExceptionMessage::FormatString(const char* format, ...) { + va_list args; + + static const unsigned kDefaultSize = 256; + std::vector buffer(kDefaultSize); + buffer.reserve(kDefaultSize); + + va_start(args, format); + int length = vsnprintf(buffer.data(), buffer.size(), format, args); + va_end(args); + + if (length < 0) + return ""; + + if (static_cast(length) >= buffer.size()) { + // vsnprintf doesn't include the NUL terminator in the length so we need to + // add space for it when growing. + buffer.reserve(length + 1); + + // We need to call va_end() and then va_start() each time we use args, as + // the contents of args is undefined after the call to vsnprintf according + // to http://man.cx/snprintf(3) + // + // Not calling va_end/va_start here happens to work on lots of systems, but + // fails e.g. on 64bit Linux. + va_start(args, format); + length = vsnprintf(buffer.data(), buffer.size(), format, args); + va_end(args); + } + + assert(static_cast(length) <= buffer.size()); + return std::string(buffer.data(), length); +} + +std::string ExceptionMessage::ArgumentNotOfType(int argument_index, const char* expected_type) { + return FormatString("parameter %d is not of type '%s'.", argument_index + 1, expected_type); +} + +std::string ExceptionMessage::ArgumentNullOrIncorrectType(int argument_index, const char* expect_type) { + return FormatString("The %d argument provided is either null, or an invalid %s object.", argument_index, expect_type); +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/exception_message.h b/bridge/bindings/qjs/exception_message.h new file mode 100644 index 0000000000..8abf01e3b4 --- /dev/null +++ b/bridge/bindings/qjs/exception_message.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ + +#include + +namespace kraken { + +class ExceptionMessage { + public: + static std::string FormatString(const char* format, ...); + + static std::string ArgumentNotOfType(int argument_index, const char* expect_type); + static std::string ArgumentNullOrIncorrectType(int argument_index, const char* expect_type); + + private: +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc new file mode 100644 index 0000000000..c9695df238 --- /dev/null +++ b/bridge/bindings/qjs/exception_state.cc @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "exception_state.h" + +namespace kraken { + +void ExceptionState::ThrowException(JSContext* ctx, ErrorType type, const std::string& message) { + switch (type) { + case ErrorType::TypeError: + exception_ = JS_ThrowTypeError(ctx, "%s", message.c_str()); + break; + case InternalError: + exception_ = JS_ThrowInternalError(ctx, "%s", message.c_str()); + break; + case RangeError: + exception_ = JS_ThrowRangeError(ctx, "%s", message.c_str()); + break; + case ReferenceError: + exception_ = JS_ThrowReferenceError(ctx, "%s", message.c_str()); + break; + case SyntaxError: + exception_ = JS_ThrowSyntaxError(ctx, "%s", message.c_str()); + break; + } +} + +void ExceptionState::ThrowException(JSContext* ctx, JSValue exception) { + exception_ = JS_DupValue(ctx, exception); +} + +bool ExceptionState::HasException() { + return !JS_IsNull(exception_); +} + +ExceptionState& ExceptionState::ReturnThis() { + return *this; +} + +JSValue ExceptionState::ToQuickJS() { + return exception_; +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h new file mode 100644 index 0000000000..b314be12dc --- /dev/null +++ b/bridge/bindings/qjs/exception_state.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_EXCEPTION_STATE_H +#define KRAKENBRIDGE_EXCEPTION_STATE_H + +#include +#include +#include "foundation/macros.h" + +#define ASSERT_NO_EXCEPTION() ExceptionState().ReturnThis() + +namespace kraken { + +enum ErrorType { TypeError, InternalError, RangeError, ReferenceError, SyntaxError }; + +// ExceptionState is a scope-like class and provides a way to store an exception. +class ExceptionState { + // ExceptionState should only allocate at stack. + KRAKEN_DISALLOW_NEW(); + + public: + void ThrowException(JSContext* ctx, ErrorType type, const std::string& message); + void ThrowException(JSContext* ctx, JSValue exception); + bool HasException(); + + ExceptionState& ReturnThis(); + + JSValue ToQuickJS(); + + private: + JSValue exception_{JS_NULL}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_EXCEPTION_STATE_H diff --git a/bridge/bindings/qjs/executing_context.cc b/bridge/bindings/qjs/executing_context.cc deleted file mode 100644 index 5c6af7802f..0000000000 --- a/bridge/bindings/qjs/executing_context.cc +++ /dev/null @@ -1,560 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "executing_context.h" -#include "bindings/qjs/bom/timer.h" -#include "bindings/qjs/bom/window.h" -#include "bindings/qjs/dom/document.h" -#include "bindings/qjs/module_manager.h" -#include "bom/dom_timer_coordinator.h" -#include "garbage_collected.h" -#include "kraken_bridge.h" -#include "qjs_patch.h" - -namespace kraken::binding::qjs { - -static std::atomic context_unique_id{0}; - -JSClassID ExecutionContext::kHostClassClassId{0}; -JSClassID ExecutionContext::kHostObjectClassId{0}; -JSClassID ExecutionContext::kHostExoticObjectClassId{0}; - -std::atomic runningContexts{0}; - -#define MAX_JS_CONTEXT 1024 -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); -} - -static JSRuntime* m_runtime{nullptr}; - -void ExecutionContextGCTracker::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - auto* context = static_cast(JS_GetContextOpaque(m_ctx)); - context->trace(rt, context->global(), mark_func); -} -void ExecutionContextGCTracker::dispose() const {} - -JSClassID ExecutionContextGCTracker::contextGcTrackerClassId{0}; - -ExecutionContext::ExecutionContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) - : contextId(contextId), _handler(handler), owner(owner), ctxInvalid_(false), uniqueId(context_unique_id++) { - // @FIXME: maybe contextId will larger than MAX_JS_CONTEXT - valid_contexts[contextId] = true; - if (contextId > running_context_list) - running_context_list = contextId; - - std::call_once(kinitJSClassIDFlag, []() { - JS_NewClassID(&kHostClassClassId); - JS_NewClassID(&kHostObjectClassId); - JS_NewClassID(&kHostExoticObjectClassId); - }); - - init_list_head(&node_job_list); - init_list_head(&module_job_list); - init_list_head(&module_callback_job_list); - init_list_head(&promise_job_list); - init_list_head(&native_function_job_list); - - if (m_runtime == nullptr) { - m_runtime = JS_NewRuntime(); - } - // Avoid stack overflow when running in multiple threads. - JS_UpdateStackTop(m_runtime); - m_ctx = JS_NewContext(m_runtime); - - timeOrigin = std::chrono::system_clock::now(); - globalObject = JS_GetGlobalObject(m_ctx); - JSValue windowGetter = JS_NewCFunction( - m_ctx, [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { return JS_GetGlobalObject(ctx); }, "get", 0); - JSAtom windowKey = JS_NewAtom(m_ctx, "window"); - JS_DefinePropertyGetSet(m_ctx, globalObject, windowKey, windowGetter, JS_UNDEFINED, JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); - JS_FreeAtom(m_ctx, windowKey); - JS_SetContextOpaque(m_ctx, this); - JS_SetHostPromiseRejectionTracker(m_runtime, promiseRejectTracker, nullptr); - - m_gcTracker = makeGarbageCollected()->initialize(m_ctx, &ExecutionContextGCTracker::contextGcTrackerClassId); - JS_DefinePropertyValueStr(m_ctx, globalObject, "_gc_tracker_", m_gcTracker->toQuickJS(), JS_PROP_NORMAL); - - runningContexts++; -} - -ExecutionContext::~ExecutionContext() { - valid_contexts[contextId] = false; - ctxInvalid_ = true; - - // Manual free nodes bound by each other. - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &node_job_list) { - auto* node = list_entry(el, NodeJob, link); - JS_FreeValue(m_ctx, node->nodeInstance->jsObject); - } - } - - // Manual free moduleListener - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &module_job_list) { - auto* module = list_entry(el, ModuleContext, link); - JS_FreeValue(m_ctx, module->callback); - delete module; - } - } - - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &module_callback_job_list) { - auto* module = list_entry(el, ModuleContext, link); - JS_FreeValue(m_ctx, module->callback); - delete module; - } - } - - // Free unresolved promise. - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &promise_job_list) { - auto* promiseContext = list_entry(el, PromiseContext, link); - JS_FreeValue(m_ctx, promiseContext->resolveFunc); - JS_FreeValue(m_ctx, promiseContext->rejectFunc); - delete promiseContext; - } - } - - // Free unreleased native_functions. - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &native_function_job_list) { - auto* job = list_entry(el, NativeFunctionContext, link); - delete job; - } - } - - // Check if current context have unhandled exceptions. - JSValue exception = JS_GetException(m_ctx); - if (JS_IsObject(exception) || JS_IsException(exception)) { - // There must be bugs in native functions from call stack frame. Someone needs to fix it if throws. - reportError(exception); - assert_m(false, "Unhandled exception found when dispose JSContext."); - } - - JS_FreeValue(m_ctx, globalObject); - JS_FreeContext(m_ctx); - - // Run GC to clean up remaining objects about m_ctx; - JS_RunGC(m_runtime); - -#if DUMP_LEAKS - if (--runningContexts == 0) { - JS_FreeRuntime(m_runtime); - m_runtime = nullptr; - } -#endif - m_ctx = nullptr; -} - -bool ExecutionContext::evaluateJavaScript(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { - std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), codeLength)); - JSValue result = JS_Eval(m_ctx, utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); - drainPendingPromiseJobs(); - bool success = handleException(&result); - JS_FreeValue(m_ctx, result); - return success; -} - -bool ExecutionContext::evaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine) { - std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), length)); - JSValue result = JS_Eval(m_ctx, utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); - drainPendingPromiseJobs(); - bool success = handleException(&result); - JS_FreeValue(m_ctx, result); - return success; -} - -bool ExecutionContext::evaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine) { - JSValue result = JS_Eval(m_ctx, code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL); - drainPendingPromiseJobs(); - bool success = handleException(&result); - JS_FreeValue(m_ctx, result); - return success; -} - -bool ExecutionContext::evaluateByteCode(uint8_t* bytes, size_t byteLength) { - JSValue obj, val; - obj = JS_ReadObject(m_ctx, bytes, byteLength, JS_READ_OBJ_BYTECODE); - if (!handleException(&obj)) - return false; - val = JS_EvalFunction(m_ctx, obj); - if (!handleException(&val)) - return false; - JS_FreeValue(m_ctx, val); - return true; -} - -bool ExecutionContext::isValid() const { - return !ctxInvalid_; -} - -int32_t ExecutionContext::getContextId() const { - assert(!ctxInvalid_ && "context has been released"); - return contextId; -} - -void* ExecutionContext::getOwner() { - assert(!ctxInvalid_ && "context has been released"); - return owner; -} - -bool ExecutionContext::handleException(JSValue* exception) { - if (JS_IsException(*exception)) { - JSValue error = JS_GetException(m_ctx); - dispatchGlobalErrorEvent(this, error); - JS_FreeValue(m_ctx, error); - return false; - } - - return true; -} - -JSValue ExecutionContext::global() { - return globalObject; -} - -JSContext* ExecutionContext::ctx() { - assert(!ctxInvalid_ && "context has been released"); - return m_ctx; -} - -JSRuntime* ExecutionContext::runtime() { - return m_runtime; -} - -void ExecutionContext::reportError(JSValueConst error) { - if (!JS_IsError(m_ctx, error)) - return; - - JSValue messageValue = JS_GetPropertyStr(m_ctx, error, "message"); - JSValue errorTypeValue = JS_GetPropertyStr(m_ctx, error, "name"); - const char* title = JS_ToCString(m_ctx, messageValue); - const char* type = JS_ToCString(m_ctx, errorTypeValue); - const char* stack = nullptr; - JSValue stackValue = JS_GetPropertyStr(m_ctx, error, "stack"); - if (!JS_IsUndefined(stackValue)) { - stack = JS_ToCString(m_ctx, stackValue); - } - - uint32_t messageLength = strlen(type) + strlen(title); - if (stack != nullptr) { - messageLength += 4 + strlen(stack); - char message[messageLength]; - sprintf(message, "%s: %s\n%s", type, title, stack); - _handler(contextId, message); - } else { - messageLength += 3; - char message[messageLength]; - sprintf(message, "%s: %s", type, title); - _handler(contextId, message); - } - - JS_FreeValue(m_ctx, errorTypeValue); - JS_FreeValue(m_ctx, messageValue); - JS_FreeValue(m_ctx, stackValue); - JS_FreeCString(m_ctx, title); - JS_FreeCString(m_ctx, stack); - JS_FreeCString(m_ctx, type); -} - -void ExecutionContext::reportErrorEvent(EventInstance* errorEvent) { - JSValue error = JS_GetPropertyStr(m_ctx, errorEvent->jsObject, "error"); - reportError(error); - JS_FreeValue(m_ctx, error); -} - -void ExecutionContext::dispatchErrorEvent(EventInstance* errorEvent) { - if (m_inDispatchErrorEvent_) { - return; - } - - dispatchErrorEventInternal(errorEvent); - reportErrorEvent(errorEvent); -} - -void ExecutionContext::dispatchErrorEventInternal(EventInstance* errorEvent) { - if (m_window == nullptr) - return; - - assert(!m_inDispatchErrorEvent_); - m_inDispatchErrorEvent_ = true; - m_window->dispatchEvent(errorEvent); - m_inDispatchErrorEvent_ = false; -} - -void ExecutionContext::drainPendingPromiseJobs() { - // should executing pending promise jobs. - JSContext* pctx; - int finished = JS_ExecutePendingJob(runtime(), &pctx); - while (finished != 0) { - finished = JS_ExecutePendingJob(runtime(), &pctx); - if (finished == -1) { - break; - } - } - - // Throw error when promise are not handled. - m_rejectedPromise.process(this); -} - -void ExecutionContext::defineGlobalProperty(const char* prop, JSValue value) { - JSAtom atom = JS_NewAtom(m_ctx, prop); - JS_SetProperty(m_ctx, globalObject, atom, value); - JS_FreeAtom(m_ctx, atom); -} - -uint8_t* ExecutionContext::dumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength) { - JSValue object = JS_Eval(m_ctx, code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY); - bool success = handleException(&object); - if (!success) - return nullptr; - uint8_t* bytes = JS_WriteObject(m_ctx, bytecodeLength, object, JS_WRITE_OBJ_BYTECODE); - JS_FreeValue(m_ctx, object); - return bytes; -} - -void ExecutionContext::dispatchGlobalErrorEvent(ExecutionContext* context, JSValueConst error) { - JSContext* ctx = context->ctx(); - auto* window = static_cast(JS_GetOpaque(context->global(), Window::classId())); - - { - JSValue errorEventConstructor = JS_GetPropertyStr(ctx, context->global(), "ErrorEvent"); - JSValue errorType = JS_NewString(ctx, "error"); - JSValue errorInit = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, errorInit, "error", JS_DupValue(ctx, error)); - JS_SetPropertyStr(ctx, errorInit, "message", JS_GetPropertyStr(ctx, error, "message")); - JS_SetPropertyStr(ctx, errorInit, "lineno", JS_GetPropertyStr(ctx, error, "lineNumber")); - JS_SetPropertyStr(ctx, errorInit, "filename", JS_GetPropertyStr(ctx, error, "fileName")); - JS_SetPropertyStr(ctx, errorInit, "colno", JS_NewUint32(ctx, 0)); - JSValue arguments[] = {errorType, errorInit}; - JSValue errorEventValue = JS_CallConstructor(context->ctx(), errorEventConstructor, 2, arguments); - if (JS_IsException(errorEventValue)) { - context->handleException(&errorEventValue); - return; - } - - auto* errorEvent = static_cast(JS_GetOpaque(errorEventValue, Event::kEventClassID)); - errorEvent->setTarget(window); - context->dispatchErrorEvent(errorEvent); - - JS_FreeValue(ctx, errorEventConstructor); - JS_FreeValue(ctx, errorEventValue); - JS_FreeValue(ctx, errorType); - JS_FreeValue(ctx, errorInit); - - context->drainPendingPromiseJobs(); - } -} - -static void dispatchPromiseRejectionEvent(const char* eventType, ExecutionContext* context, JSValueConst promise, JSValueConst error) { - JSContext* ctx = context->ctx(); - auto* window = static_cast(JS_GetOpaque(context->global(), Window::classId())); - - // Trigger PromiseRejectionEvent(unhandledrejection) event. - { - JSValue PromiseRejectionEventValue = JS_GetPropertyStr(ctx, context->global(), "PromiseRejectionEvent"); - JSValue errorType = JS_NewString(ctx, eventType); - JSValue errorInit = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, errorInit, "promise", JS_DupValue(ctx, promise)); - JS_SetPropertyStr(ctx, errorInit, "reason", JS_DupValue(ctx, error)); - JSValue arguments[] = {errorType, errorInit}; - JSValue rejectEventValue = JS_CallConstructor(context->ctx(), PromiseRejectionEventValue, 2, arguments); - if (JS_IsException(rejectEventValue)) { - context->handleException(&rejectEventValue); - return; - } - - auto* rejectEvent = static_cast(JS_GetOpaque(rejectEventValue, Event::kEventClassID)); - rejectEvent->setTarget(window); - window->dispatchEvent(rejectEvent); - - JS_FreeValue(ctx, errorType); - JS_FreeValue(ctx, errorInit); - JS_FreeValue(ctx, rejectEventValue); - JS_FreeValue(ctx, PromiseRejectionEventValue); - - context->drainPendingPromiseJobs(); - } -} - -void ExecutionContext::dispatchGlobalUnhandledRejectionEvent(ExecutionContext* context, JSValueConst promise, JSValueConst error) { - // Trigger onerror event. - dispatchGlobalErrorEvent(context, error); - - // Trigger unhandledRejection event. - dispatchPromiseRejectionEvent("unhandledrejection", context, promise, error); -} - -void ExecutionContext::dispatchGlobalRejectionHandledEvent(ExecutionContext* context, JSValue promise, JSValue error) { - // Trigger rejectionhandled event. - dispatchPromiseRejectionEvent("rejectionhandled", context, promise, error); -} - -void ExecutionContext::promiseRejectTracker(JSContext* ctx, JSValue promise, JSValue reason, int is_handled, void* opaque) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - // The unhandledrejection event is the promise-equivalent of the global error event, which is fired for uncaught exceptions. - // Because a rejected promise could be handled after the fact, by attaching catch(onRejected) or then(onFulfilled, onRejected) to it, - // the additional rejectionhandled event is needed to indicate that a promise which was previously rejected should no longer be considered unhandled. - if (is_handled) { - context->m_rejectedPromise.trackHandledPromiseRejection(context, promise, reason); - } else { - context->m_rejectedPromise.trackUnhandledPromiseRejection(context, promise, reason); - } -} - -DOMTimerCoordinator* ExecutionContext::timers() { - return &m_timers; -} - -std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue value) { - bool isValueString = true; - if (JS_IsNull(value)) { - value = JS_NewString(ctx, ""); - isValueString = false; - } else if (!JS_IsString(value)) { - value = JS_ToString(ctx, value); - isValueString = false; - } - - uint32_t length; - uint16_t* buffer = JS_ToUnicode(ctx, value, &length); - std::unique_ptr ptr = std::make_unique(); - ptr->string = buffer; - ptr->length = length; - - if (!isValueString) { - JS_FreeValue(ctx, value); - } - return ptr; -} - -void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01) { - if (!JS_IsString(key)) - return; - - uint32_t length; - uint16_t* buffer = JS_ToUnicode(ctx, key, &length); - args_01.string = buffer; - args_01.length = length; -} - -std::unique_ptr stringToNativeString(const std::string& string) { - std::u16string utf16; - fromUTF8(string, utf16); - NativeString tmp{}; - tmp.string = reinterpret_cast(utf16.c_str()); - tmp.length = utf16.size(); - return std::unique_ptr(tmp.clone()); -} - -std::unique_ptr atomToNativeString(JSContext* ctx, JSAtom atom) { - JSValue stringValue = JS_AtomToString(ctx, atom); - std::unique_ptr string = jsValueToNativeString(ctx, stringValue); - JS_FreeValue(ctx, stringValue); - return string; -} - -std::string jsValueToStdString(JSContext* ctx, JSValue& value) { - const char* cString = JS_ToCString(ctx, value); - std::string str = std::string(cString); - JS_FreeCString(ctx, cString); - return str; -} - -std::string jsAtomToStdString(JSContext* ctx, JSAtom atom) { - const char* cstr = JS_AtomToCString(ctx, atom); - std::string str = std::string(cstr); - JS_FreeCString(ctx, cstr); - return str; -} - -// An lock free context validator. -bool isContextValid(int32_t contextId) { - if (contextId > running_context_list) - return false; - return valid_contexts[contextId]; -} - -void arrayPushValue(JSContext* ctx, JSValue array, JSValue val) { - JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); - JSValue arguments[] = {val}; - JSValue result = JS_Call(ctx, pushMethod, array, 1, arguments); - JS_FreeValue(ctx, pushMethod); - JS_FreeValue(ctx, result); -} - -void arraySpliceValue(JSContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount) { - JSValue spliceMethod = JS_GetPropertyStr(ctx, array, "splice"); - JSValue arguments[] = {JS_NewUint32(ctx, start), JS_NewUint32(ctx, deleteCount)}; - JSValue result = JS_Call(ctx, spliceMethod, array, 2, arguments); - JS_FreeValue(ctx, spliceMethod); - JS_FreeValue(ctx, result); -} - -void arraySpliceValue(JSContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount, JSValue replacedValue) { - JSValue spliceMethod = JS_GetPropertyStr(ctx, array, "splice"); - JSValue arguments[] = {JS_NewUint32(ctx, start), JS_NewUint32(ctx, deleteCount), replacedValue}; - JSValue result = JS_Call(ctx, spliceMethod, array, 3, arguments); - JS_FreeValue(ctx, spliceMethod); - JS_FreeValue(ctx, result); -} - -void arrayInsert(JSContext* ctx, JSValue array, uint32_t start, JSValue targetValue) { - JSValue spliceMethod = JS_GetPropertyStr(ctx, array, "splice"); - JSValue arguments[] = {JS_NewUint32(ctx, start), JS_NewUint32(ctx, 0), targetValue}; - JSValue result = JS_Call(ctx, spliceMethod, array, 3, arguments); - JS_FreeValue(ctx, spliceMethod); - JS_FreeValue(ctx, result); -} - -int32_t arrayGetLength(JSContext* ctx, JSValue array) { - JSValue lenVal = JS_GetPropertyStr(ctx, array, "length"); - int32_t len; - JS_ToInt32(ctx, &len, lenVal); - JS_FreeValue(ctx, lenVal); - return len; -} - -int32_t arrayFindIdx(JSContext* ctx, JSValue array, JSValue target) { - int32_t len = arrayGetLength(ctx, array); - for (int i = 0; i < len; i++) { - JSValue v = JS_GetPropertyUint32(ctx, array, i); - if (JS_VALUE_GET_PTR(v) == JS_VALUE_GET_PTR(target)) { - JS_FreeValue(ctx, v); - return i; - }; - JS_FreeValue(ctx, v); - } - return -1; -} - -JSValue objectGetKeys(JSContext* ctx, JSValue obj) { - JSValue globalObject = JS_GetGlobalObject(ctx); - JSValue object = JS_GetPropertyStr(ctx, globalObject, "Object"); - JSValue keysFunc = JS_GetPropertyStr(ctx, object, "keys"); - - JSValue result = JS_Call(ctx, keysFunc, obj, 1, &obj); - - JS_FreeValue(ctx, keysFunc); - JS_FreeValue(ctx, object); - JS_FreeValue(ctx, globalObject); - - return result; -} - -void ExecutionContext::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - m_timers.trace(rt, JS_NULL, mark_func); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/executing_context.h b/bridge/bindings/qjs/executing_context.h deleted file mode 100644 index 601e534253..0000000000 --- a/bridge/bindings/qjs/executing_context.h +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_JS_CONTEXT_H -#define KRAKENBRIDGE_JS_CONTEXT_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "bindings/qjs/bom/dom_timer_coordinator.h" -#include "foundation/ui_command_buffer.h" -#include "garbage_collected.h" -#include "js_context_macros.h" -#include "kraken_foundation.h" -#include "qjs_patch.h" -#include "rejected_promises.h" - -using JSExceptionHandler = std::function; - -namespace kraken::binding::qjs { - -static std::once_flag kinitJSClassIDFlag; - -class WindowInstance; -class DocumentInstance; -class ExecutionContext; -class EventInstance; -struct DOMTimerCallbackContext; - -std::string jsAtomToStdString(JSContext* ctx, JSAtom atom); - -static inline bool isNumberIndex(const std::string& name) { - if (name.empty()) - return false; - char f = name[0]; - return f >= '0' && f <= '9'; -} - -struct PromiseContext { - void* data; - ExecutionContext* context; - JSValue resolveFunc; - JSValue rejectFunc; - JSValue promise; - list_head link; -}; - -bool isContextValid(int32_t contextId); - -class ExecutionContextGCTracker : public GarbageCollected { - public: - static JSClassID contextGcTrackerClassId; - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; - void dispose() const override; - - private: -}; - -// An environment in which script can execute. This class exposes the common -// properties of script execution environments on the kraken. -// Window : Document : ExecutionContext = 1 : 1 : 1 at any point in time. -class ExecutionContext { - public: - ExecutionContext() = delete; - ExecutionContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); - ~ExecutionContext(); - - bool evaluateJavaScript(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine); - 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; - JSValue global(); - JSContext* ctx(); - static JSRuntime* runtime(); - int32_t getContextId() const; - void* getOwner(); - bool handleException(JSValue* exc); - void drainPendingPromiseJobs(); - void defineGlobalProperty(const char* prop, JSValueConst value); - uint8_t* dumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength); - - // Gets the DOMTimerCoordinator which maintains the "active timer - // list" of tasks created by setTimeout and setInterval. The - // DOMTimerCoordinator is owned by the ExecutionContext and should - // not be used after the ExecutionContext is destroyed. - DOMTimerCoordinator* timers(); - - FORCE_INLINE DocumentInstance* document() { return m_document; }; - FORCE_INLINE WindowInstance* window() { return m_window; } - FORCE_INLINE foundation::UICommandBuffer* uiCommandBuffer() { return &m_commandBuffer; }; - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func); - - std::chrono::time_point timeOrigin; - std::unordered_map constructorMap; - - int32_t uniqueId; - struct list_head node_job_list; - struct list_head module_job_list; - struct list_head module_callback_job_list; - struct list_head promise_job_list; - struct list_head native_function_job_list; - - static JSClassID kHostClassClassId; - static JSClassID kHostObjectClassId; - static JSClassID kHostExoticObjectClassId; - - static void dispatchGlobalUnhandledRejectionEvent(ExecutionContext* context, JSValueConst promise, JSValueConst error); - static void dispatchGlobalRejectionHandledEvent(ExecutionContext* context, JSValueConst promise, JSValueConst error); - static void dispatchGlobalErrorEvent(ExecutionContext* context, JSValueConst error); - - void reportError(JSValueConst error); - void reportErrorEvent(EventInstance* errorEvent); - void dispatchErrorEvent(EventInstance* errorEvent); - void dispatchErrorEventInternal(EventInstance* errorEvent); - - private: - static void promiseRejectTracker(JSContext* ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void* opaque); - - int32_t contextId; - JSExceptionHandler _handler; - void* owner; - JSValue globalObject{JS_NULL}; - bool ctxInvalid_{false}; - JSContext* m_ctx{nullptr}; - bool m_inDispatchErrorEvent_{false}; - friend WindowInstance; - friend DocumentInstance; - WindowInstance* m_window{nullptr}; - DocumentInstance* m_document{nullptr}; - DOMTimerCoordinator m_timers; - ExecutionContextGCTracker* m_gcTracker{nullptr}; - foundation::UICommandBuffer m_commandBuffer{contextId}; - RejectedPromises m_rejectedPromise; -}; - -// The read object's method or properties via Proxy, we should redirect this_val from Proxy into target property of -// proxy object. -static JSValue handleCallThisOnProxy(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int data_len, JSValueConst* data) { - JSValue f = data[0]; - JSValue result; - if (JS_IsProxy(this_val)) { - result = JS_Call(ctx, f, JS_GetProxyTarget(this_val), argc, argv); - } else { - // If this_val is undefined or null, this_val should set to globalThis. - if (JS_IsUndefined(this_val) || JS_IsNull(this_val)) { - this_val = JS_GetGlobalObject(ctx); - result = JS_Call(ctx, f, this_val, argc, argv); - JS_FreeValue(ctx, this_val); - } else { - result = JS_Call(ctx, f, this_val, argc, argv); - } - } - return result; -} - -class ObjectProperty { - KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(ObjectProperty); - - public: - ObjectProperty() = delete; - - // Define a property on object with a getter and setter function. - explicit ObjectProperty(ExecutionContext* context, JSValueConst thisObject, const std::string& property, JSCFunction getterFunction, JSCFunction setterFunction) { - // Getter on jsObject works well with all conditions. - // We create an getter function and define to jsObject directly. - JSAtom propertyKeyAtom = JS_NewAtom(context->ctx(), property.c_str()); - JSValue getter = JS_NewCFunction(context->ctx(), getterFunction, "getter", 0); - JSValue getterProxy = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, 0, 0, 1, &getter); - - // Getter on jsObject works well with all conditions. - // We create an getter function and define to jsObject directly. - JSValue setter = JS_NewCFunction(context->ctx(), setterFunction, "setter", 0); - JSValue setterProxy = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, 1, 0, 1, &setter); - - // Define getter and setter property. - JS_DefinePropertyGetSet(context->ctx(), thisObject, propertyKeyAtom, getterProxy, setterProxy, JS_PROP_NORMAL | JS_PROP_ENUMERABLE); - - JS_FreeAtom(context->ctx(), propertyKeyAtom); - JS_FreeValue(context->ctx(), getter); - JS_FreeValue(context->ctx(), setter); - }; - - explicit ObjectProperty(ExecutionContext* context, JSValueConst thisObject, const std::string& property, JSCFunction getterFunction) { - // Getter on jsObject works well with all conditions. - // We create an getter function and define to jsObject directly. - JSAtom propertyKeyAtom = JS_NewAtom(context->ctx(), property.c_str()); - JSValue getter = JS_NewCFunction(context->ctx(), getterFunction, "getter", 0); - JSValue getterProxy = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, 0, 0, 1, &getter); - JS_DefinePropertyGetSet(context->ctx(), thisObject, propertyKeyAtom, getterProxy, JS_UNDEFINED, JS_PROP_NORMAL | JS_PROP_ENUMERABLE); - JS_FreeAtom(context->ctx(), propertyKeyAtom); - JS_FreeValue(context->ctx(), getter); - }; - - // Define an property on object with a JSValue. - explicit ObjectProperty(ExecutionContext* context, JSValueConst thisObject, const char* property, JSValue value) : m_value(value) { - JS_DefinePropertyValueStr(context->ctx(), thisObject, property, value, JS_PROP_ENUMERABLE); - } - - JSValue value() const { return m_value; } - - private: - JSValue m_value{JS_NULL}; -}; - -class ObjectFunction { - KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(ObjectFunction); - - public: - ObjectFunction() = delete; - explicit ObjectFunction(ExecutionContext* context, JSValueConst thisObject, const char* functionName, JSCFunction function, int argc) { - JSValue f = JS_NewCFunction(context->ctx(), function, functionName, argc); - JSValue pf = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, argc, 0, 1, &f); - JSAtom key = JS_NewAtom(context->ctx(), functionName); - - JS_FreeValue(context->ctx(), f); - -// We should avoid overwrite exist property functions. -#ifdef DEBUG - assert_m(JS_HasProperty(context->ctx(), thisObject, key) == 0, (std::string("Found exist function property: ") + std::string(functionName)).c_str()); -#endif - - JS_DefinePropertyValue(context->ctx(), thisObject, key, pf, JS_PROP_ENUMERABLE); - JS_FreeAtom(context->ctx(), key); - }; -}; - -class JSValueHolder { - public: - JSValueHolder() = delete; - explicit JSValueHolder(JSContext* ctx, JSValue value) : m_value(value), m_ctx(ctx){}; - ~JSValueHolder() { JS_FreeValue(m_ctx, m_value); } - inline void value(JSValue value) { - if (!JS_IsNull(m_value)) { - JS_FreeValue(m_ctx, m_value); - } - m_value = JS_DupValue(m_ctx, value); - }; - inline JSValue value() const { return JS_DupValue(m_ctx, m_value); } - - private: - JSContext* m_ctx{nullptr}; - JSValue m_value{JS_NULL}; -}; - -std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); - -// Convert to string and return a full copy of NativeString from JSValue. -std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue value); - -void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01); - -// Encode utf-8 to utf-16, and return a full copy of NativeString. -std::unique_ptr stringToNativeString(const std::string& string); - -// Return a full copy of NativeString form JSAtom. -std::unique_ptr atomToNativeString(JSContext* ctx, JSAtom atom); - -// Convert to string and return a full copy of std::string from JSValue. -std::string jsValueToStdString(JSContext* ctx, JSValue& value); - -// Return a full copy of std::string form JSAtom. -std::string jsAtomToStdString(JSContext* ctx, JSAtom atom); - -// JS array operation utilities. -void arrayPushValue(JSContext* ctx, JSValue array, JSValue val); -void arrayInsert(JSContext* ctx, JSValue array, uint32_t start, JSValue targetValue); -int32_t arrayGetLength(JSContext* ctx, JSValue array); -int32_t arrayFindIdx(JSContext* ctx, JSValue array, JSValue target); -void arraySpliceValue(JSContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount); -void arraySpliceValue(JSContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount, JSValue replacedValue); - -// JS object operation utilities. -JSValue objectGetKeys(JSContext* ctx, JSValue obj); - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_JS_CONTEXT_H diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h deleted file mode 100644 index 071f7b1a24..0000000000 --- a/bridge/bindings/qjs/garbage_collected.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_GARBAGE_COLLECTED_H -#define KRAKENBRIDGE_GARBAGE_COLLECTED_H - -#include -#include "include/kraken_foundation.h" -#include "qjs_patch.h" - -namespace kraken::binding::qjs { - -template -class MakeGarbageCollectedTrait; - -/** - * Base class for GC managed objects. Only descendent types of `GarbageCollected` - * can be constructed using `MakeGarbageCollected()`. Must be inherited from as - * left-most base class. - * - * \code - * // Example using final class. - * class FinalType final : public GarbageCollected { - * public: - * void Trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) const { - * // trace all memory wants to collected by GC. - * } - * }; - */ -template -class GarbageCollected { - public: - using ParentMostGarbageCollectedType = T; - - virtual T* initialize(JSContext* ctx, JSClassID* classId); - - // Must use MakeGarbageCollected. - void* operator new(size_t) = delete; - void* operator new[](size_t) = delete; - - // The garbage collector is taking care of reclaiming the object. - void operator delete(void*) = delete; - void operator delete[](void*) = delete; - - /** - * This Trace method must be override by objects inheriting from - * GarbageCollected. - */ - virtual void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) const = 0; - - /** - * Called before underline JavaScript object been collected by GC. - * Note: JS_FreeValue and JS_FreeAtom is not available, use JS_FreeValueRT and JS_FreeAtomRT instead. - */ - virtual void dispose() const = 0; - - /** - * Specifies a name for the garbage-collected object. Such names will never - * be hidden, as they are explicitly specified by the user of this API. - * - * @returns a human readable name for the object. - */ - [[nodiscard]] FORCE_INLINE virtual const char* getHumanReadableName() const { return ""; }; - - FORCE_INLINE JSValue toQuickJS() { return jsObject; }; - - FORCE_INLINE JSContext* ctx() { return m_ctx; } - - // A anchor to efficiently bind the current object to a linked-list. - list_head link; - - protected: - JSValue jsObject{JS_NULL}; - JSContext* m_ctx{nullptr}; - JSRuntime* m_runtime{nullptr}; - GarbageCollected(){}; - friend class MakeGarbageCollectedTrait; -}; - -template -class MakeGarbageCollectedTrait { - public: - template - static T* allocate(Args&&... args) { - T* object = ::new T(std::forward(args)...); - return object; - } - - friend GarbageCollected; -}; - -template -T* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId) { - JSRuntime* runtime = JS_GetRuntime(ctx); - - /// When classId is 0, it means this class are not initialized. We should create a JSClassDef to describe the behavior of this class and associate with classID. - /// ClassId should be a static value to make sure JSClassDef when this class are created at the first class. - if (*classId == 0 || !JS_HasClassId(runtime, *classId)) { - /// Allocate a new unique classID from QuickJS. - JS_NewClassID(classId); - /// Basic template to describe the behavior about this class. - JSClassDef def{}; - - def.class_name = getHumanReadableName(); - - /// This callback will be called when QuickJS GC is running at marking stage. - /// Users of this class should override `void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func)` to tell GC - /// which member of their class should be collected by GC. - def.gc_mark = [](JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) { - auto* object = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); - object->trace(rt, val, mark_func); - }; - - /// This callback will be called when QuickJS GC will release the `jsObject` object memory of this class. - /// The deconstruct method of this class will be called and all memory about this class will be freed when finalize completed. - def.finalizer = [](JSRuntime* rt, JSValue val) { - auto* object = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); - object->dispose(); - free(object); - }; - - JS_NewClass(runtime, *classId, &def); - } - - /// The JavaScript object underline this class. This `jsObject` is the JavaScript object which can be directly access within JavaScript code. - /// When the reference count of `jsObject` decrease to 0, QuickJS will trigger `finalizer` callback and free `jsObject` memory. - /// When QuickJS GC found `jsObject` at marking stage, `gc_mark` callback will be triggered. - jsObject = JS_NewObjectClass(ctx, *classId); - JS_SetOpaque(jsObject, this); - - m_ctx = ctx; - m_runtime = JS_GetRuntime(m_ctx); - - return static_cast(this); -} - -template -T* makeGarbageCollected(Args&&... args) { - static_assert(std::is_base_of::value, - "U of GarbageCollected must be a base of T. Check " - "GarbageCollected base class inheritance."); - return MakeGarbageCollectedTrait::allocate(std::forward(args)...); -} - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_GARBAGE_COLLECTED_H diff --git a/bridge/bindings/qjs/garbage_collected_test.cc b/bridge/bindings/qjs/garbage_collected_test.cc new file mode 100644 index 0000000000..3badad68e9 --- /dev/null +++ b/bridge/bindings/qjs/garbage_collected_test.cc @@ -0,0 +1,587 @@ +///* +// * Copyright (C) 2021 Alibaba Inc. All rights reserved. +// * Author: Kraken Team. +// */ +// +//#include "host_class.h" +//#include +//#include "gtest/gtest.h" +//#include "kraken_test_env.h" +//#include "page.h" +// +// namespace kraken { +// +// class ParentClass : public HostClass { +// public: +// explicit ParentClass(ExecutionContext* context) : HostClass(context, "ParentClass") {} +// JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValueConst* argv) +// override { return HostClass::instanceConstructor(ctx, func_obj, this_val, argc, argv); +// } +// +// OBJECT_INSTANCE(ParentClass); +// +// static JSValue foo(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, +// 20); } +// +// private: +// ObjectFunction m_foo{m_context, m_prototypeObject, "foo", foo, 0}; +//}; +// +// class SampleClass; +// static JSClassID kSampleClassId{0}; +// +// class SampleClassInstance : public Instance { +// public: +// explicit SampleClassInstance(HostClass* sampleClass) : Instance(sampleClass, "SampleClass", nullptr, kSampleClassId, +// finalizer){}; +// +// private: +// static void finalizer(JSRuntime* rt, JSValue v) { +// auto* instance = static_cast(JS_GetOpaque(v, kSampleClassId)); +// if (instance->context()->isValid()) { +// JS_FreeValue(instance->m_ctx, instance->jsObject); +// } +// delete instance; +// } +//}; +// +// std::once_flag kSampleClassOnceFlag; +// class SampleClass : public ParentClass { +// public: +// explicit SampleClass(ExecutionContext* context) : ParentClass(context) { +// std::call_once(kSampleClassOnceFlag, []() { JS_NewClassID(&kSampleClassId); }); +// JS_SetPrototype(m_ctx, m_prototypeObject, ParentClass::instance(m_context)->prototype()); +// } +// JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override { +// auto* sampleClass = static_cast(JS_GetOpaque(func_obj, ExecutionContext::kHostClassClassId)); +// auto* instance = new SampleClassInstance(sampleClass); +// return instance->jsObject; +// } +// ~SampleClass() {} +// +// private: +// static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, +// 10); } +// +// ObjectFunction m_f{m_context, m_prototypeObject, "f", f, 0}; +//}; +// +// TEST(HostClass, newInstance) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "10"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleClass(context.get()); +// auto* parentObject = ParentClass::instance(context.get()); +// context->defineGlobalProperty("SampleClass", sampleObject->jsObject); +// context->defineGlobalProperty("ParentClass", parentObject->jsObject); +// const char* code = "let obj = new SampleClass(1,2,3,4); console.log(obj.f())"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +// TEST(HostClass, instanceOf) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "true"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// errorCalled = true; +// KRAKEN_LOG(VERBOSE) << errmsg; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleClass(context.get()); +// auto* parentObject = ParentClass::instance(context.get()); +// // Test for C API +// context->defineGlobalProperty("SampleClass", sampleObject->jsObject); +// context->defineGlobalProperty("ParentClass", parentObject->jsObject); +// JSValue args[] = {}; +// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); +// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, parentObject->jsObject); +// EXPECT_EQ(isInstanceof, true); +// JS_FreeValue(context->ctx(), object); +// +// // Test with Javascript +// const char* code = "let obj = new SampleClass(1,2,3,4); \n console.log(obj instanceof SampleClass)"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +// TEST(HostClass, inheritance) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "20"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// errorCalled = true; +// KRAKEN_LOG(VERBOSE) << errmsg; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleClass(context.get()); +// +// auto* parentObject = ParentClass::instance(context.get()); +// context->defineGlobalProperty("ParentClass", parentObject->jsObject); +// +// context->defineGlobalProperty("SampleClass", sampleObject->jsObject); +// +// const char* code = +// "let obj = new SampleClass(1,2,3,4);\n" +// "console.log(obj.foo())"; +// context->evaluateJavaScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +// TEST(HostClass, inherintanceInJavaScript) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "TEST 10 20"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// errorCalled = true; +// KRAKEN_LOG(VERBOSE) << errmsg; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleClass(context.get()); +// +// auto* parentObject = ParentClass::instance(context.get()); +// context->defineGlobalProperty("ParentClass", parentObject->jsObject); +// +// context->defineGlobalProperty("SampleClass", sampleObject->jsObject); +// +// const char* code = R"( +// class Demo extends SampleClass { +// constructor(name) { +// super(); +// this.name = name; +// } +// +// getName() { +// return this.name.toUpperCase(); +// } +//} +// let demo = new Demo('test'); +// console.log(demo.getName(), demo.f(), demo.foo()); +//)"; +// context->evaluateJavaScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +// TEST(HostClass, haveFunctionProtoMethods) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "ƒ ()"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// errorCalled = true; +// KRAKEN_LOG(VERBOSE) << errmsg; +// }); +// auto& context = bridge->getContext(); +// auto* parentObject = ParentClass::instance(context.get()); +// context->defineGlobalProperty("ParentClass", parentObject->jsObject); +// +// const char* code = R"( +// class Demo extends ParentClass { +// constructor(name) { +// super(); +// this.name = name; +// } +// +// getName() { +// return this.name.toUpperCase(); +// } +//} +// console.log(Demo.call); +//)"; +// context->evaluateJavaScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +// TEST(HostClass, multipleInstance) { +// bool static errorCalled = false; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// errorCalled = true; +// KRAKEN_LOG(VERBOSE) << errmsg; +// }); +// auto& context = bridge->getContext(); +// +// auto* parentObject = ParentClass::instance(context.get()); +// context->defineGlobalProperty("ParentClass", parentObject->jsObject); +// +// // Test for C API 1 +// { +// auto* sampleObject = new SampleClass(context.get()); +// context->defineGlobalProperty("SampleClass1", sampleObject->jsObject); +// JSValue args[] = {}; +// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); +// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); +// EXPECT_EQ(isInstanceof, true); +// JS_FreeValue(context->ctx(), object); +// } +// +// // Test for C API 2 +// { +// auto* sampleObject = new SampleClass(context.get()); +// context->defineGlobalProperty("SampleClass2", sampleObject->jsObject); +// JSValue args[] = {}; +// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); +// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); +// EXPECT_EQ(isInstanceof, true); +// JS_FreeValue(context->ctx(), object); +// } +// +// { +// auto* sampleObject = new SampleClass(context.get()); +// context->defineGlobalProperty("SampleClass3", sampleObject->jsObject); +// JSValue args[] = {}; +// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); +// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); +// EXPECT_EQ(isInstanceof, true); +// JS_FreeValue(context->ctx(), object); +// } +// +// { +// auto* sampleObject = new SampleClass(context.get()); +// context->defineGlobalProperty("SampleClass4", sampleObject->jsObject); +// JSValue args[] = {}; +// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); +// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); +// EXPECT_EQ(isInstanceof, true); +// JS_FreeValue(context->ctx(), object); +// } +// +// EXPECT_EQ(errorCalled, false); +//} +// +// std::once_flag kExoticClassOnceFlag; +// +// class ExoticClassInstance; +// class ExoticClass : public HostClass { +// public: +// static JSClassID exoticClassID; +// ExoticClass() = delete; +// explicit ExoticClass(ExecutionContext* context) : HostClass(context, "ExoticClass") { +// std::call_once(kExoticClassOnceFlag, []() { JS_NewClassID(&exoticClassID); }); +// } +// JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv); +// +// private: +// friend ExoticClassInstance; +//}; +// +// JSClassID ExoticClass::exoticClassID{0}; +// static bool exoticClassFreed = false; +// +// class ExoticClassInstance : public Instance { +// public: +// ExoticClassInstance() = delete; +// static JSClassExoticMethods methods; +// +// explicit ExoticClassInstance(ExoticClass* exoticClass) : Instance(exoticClass, "ExoticClass", &methods, +// ExoticClass::exoticClassID, finalizer){}; +// +// static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { +// auto* instance = static_cast(JS_GetOpaque(obj, ExoticClass::exoticClassID)); +// auto* prototype = static_cast(instance->prototype()); +// if (JS_HasProperty(ctx, prototype->m_prototypeObject, atom)) { +// return JS_GetProperty(ctx, prototype->m_prototypeObject, atom); +// } +// +// if (instance->m_properties.count(atom) > 0) { +// return instance->m_properties[atom]; +// } +// +// return JS_NULL; +// }; +// +// static void finalizer(JSRuntime* rt, JSValue val) { +// auto* instance = static_cast(JS_GetOpaque(val, ExoticClass::exoticClassID)); +// if (instance->context()->isValid()) { +// JS_FreeValue(instance->m_ctx, instance->jsObject); +// } +// delete instance; +// }; +// +// static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int +// flags) { +// auto* instance = static_cast(JS_GetOpaque(obj, ExoticClass::exoticClassID)); +// instance->m_properties[atom] = JS_DupValue(ctx, value); +// return 0; +// } +// ~ExoticClassInstance() { exoticClassFreed = true; } +// friend ExoticClass; +// +// class ClassNamePropertyDescriptor { +// public: +// static JSValue getter(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* instance = static_cast(JS_GetOpaque(this_val, ExoticClass::exoticClassID)); +// return JS_NewFloat64(ctx, instance->classValue); +// }; +// static JSValue setter(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* instance = static_cast(JS_GetOpaque(this_val, ExoticClass::exoticClassID)); +// double v; +// JS_ToFloat64(ctx, &v, argv[0]); +// instance->classValue = v; +// return JS_NULL; +// }; +// }; +// ObjectProperty m_getClassName{m_context, jsObject, "className", ClassNamePropertyDescriptor::getter, +// ClassNamePropertyDescriptor::setter}; +// +// private: +// std::unordered_map m_properties; +// double classValue{100.0}; +//}; +// +// JSClassExoticMethods ExoticClassInstance::methods{nullptr, nullptr, nullptr, nullptr, nullptr, getProperty, +// setProperty}; +// +// JSValue ExoticClass::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) +// { +// return (new ExoticClassInstance(this))->jsObject; +//} +// +// TEST(HostClass, exoticClass) { +// bool static errorCalled = false; +// bool static logCalled = false; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "10"); +// }; +// +// auto& context = bridge->getContext(); +// auto* constructor = new ExoticClass(context.get()); +// context->defineGlobalProperty("ExoticClass", constructor->jsObject); +// +// std::string code = +// "globalThis.obj = new ExoticClass();" +// "var key = 'onclick'; " +// "var otherKey = 'o' + 'n' + 'c' + 'l' + 'i' + 'c' + 'k';" +// "obj[key] = function() {return 10;};" +// "console.log(obj[otherKey]());"; +// context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +// TEST(HostClass, setExoticClassProperty) { +// bool static errorCalled = false; +// bool static logCalled = false; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "200"); +// }; +// +// auto& context = bridge->getContext(); +// auto* constructor = new ExoticClass(context.get()); +// context->defineGlobalProperty("ExoticClass", constructor->jsObject); +// +// std::string code = +// "var obj = new ExoticClass();" +// "obj.className = 200.0;" +// "console.log(obj.className);"; +// context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(exoticClassFreed, true); +// EXPECT_EQ(logCalled, true); +//} +// +//} // namespace kraken + +///* +// * Copyright (C) 2021 Alibaba Inc. All rights reserved. +// * Author: Kraken Team. +// */ +// +//#include "host_object.h" +//#include +//#include "executing_context.h" +//#include "kraken_test_env.h" +//#include "page.h" +// +// namespace kraken { +// +// static bool isSampleFree = false; +// +// class SampleObject : public HostObject { +// public: +// explicit SampleObject(ExecutionContext* context) : HostObject(context, "SampleObject"){}; +// ~SampleObject() { isSampleFree = true; } +// +// private: +// class FooPropertyDescriptor { +// public: +// static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +// auto* sampleObject = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// return JS_NewFloat64(ctx, sampleObject->m_foo); +// } +// static JSValue setter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +// auto* sampleObject = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// double f; +// JS_ToFloat64(ctx, &f, argv[0]); +// sampleObject->m_foo = f; +// return JS_NULL; +// } +// }; +// +// static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +// double v; +// JS_ToFloat64(ctx, &v, argv[0]); +// return JS_NewFloat64(ctx, 10 + v); +// } +// +// double m_foo{0}; +// ObjectProperty m_width{m_context, jsObject, "foo", FooPropertyDescriptor::getter, FooPropertyDescriptor::setter}; +// ObjectFunction m_f{m_context, jsObject, "f", f, 1}; +//}; +// +// TEST(HostObject, defineProperty) { +// bool static logCalled = false; +// bool static errorCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// +// EXPECT_STREQ(message.c_str(), "{f: ƒ (), foo: 1}"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleObject(context.get()); +// JSValue object = sampleObject->jsObject; +// context->defineGlobalProperty("o", object); +// const char* code = "o.foo++; console.log(o);"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(logCalled, true); +// EXPECT_EQ(errorCalled, false); +//} +// +// TEST(ObjectProperty, worksWithProxy) { +// bool static logCalled = false; +// bool static errorCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "0"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleObject(context.get()); +// JSValue object = sampleObject->jsObject; +// context->defineGlobalProperty("o", object); +// std::string code = std::string(R"( +// let p = new Proxy(o, { +// get(target, key, receiver) { +// return Reflect.get(target, key, receiver); +// } +//}); +// console.log(p.foo); +//)"); +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// +// EXPECT_EQ(logCalled, true); +// EXPECT_EQ(errorCalled, false); +//} +// +// TEST(HostObject, defineFunction) { +// bool static logCalled = false; +// bool static errorCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "20"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleObject(context.get()); +// JSValue object = sampleObject->jsObject; +// context->defineGlobalProperty("o", object); +// const char* code = "console.log(o.f(10))"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(logCalled, true); +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(isSampleFree, true); +//} +// +// class SampleExoticHostObject : public ExoticHostObject { +// public: +// explicit SampleExoticHostObject(ExecutionContext* context) : ExoticHostObject(context, "SampleObject"){}; +// ~SampleExoticHostObject() { isSampleFree = true; } +// +// JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); +// int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int +// flags); +// +// private: +//}; +// +// JSValue SampleExoticHostObject::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { +// return JS_NewFloat64(ctx, 100.0); +//} +// int SampleExoticHostObject::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, +// int flags) { +// return 0; +//} +// +// TEST(ExoticHostObject, overriteGetterSetter) { +// bool static logCalled = false; +// bool static errorCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "100"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleExoticHostObject(context.get()); +// JSValue object = sampleObject->jsObject; +// context->defineGlobalProperty("o", object); +// const char* code = "console.log(o.abc)"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(logCalled, true); +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(isSampleFree, true); +//} +// +//} // namespace kraken diff --git a/bridge/bindings/qjs/generated_code_helper.h b/bridge/bindings/qjs/generated_code_helper.h new file mode 100644 index 0000000000..9bf8d3bf52 --- /dev/null +++ b/bridge/bindings/qjs/generated_code_helper.h @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_GENERATED_CODE_HELPER_H +#define KRAKENBRIDGE_GENERATED_CODE_HELPER_H + +#include "atomic_string.h" +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/qjs_interface_bridge.h" +#include "script_value.h" + +#endif diff --git a/bridge/bindings/qjs/heap_hashmap.h b/bridge/bindings/qjs/heap_hashmap.h index 76a75c4457..d2871cea77 100644 --- a/bridge/bindings/qjs/heap_hashmap.h +++ b/bridge/bindings/qjs/heap_hashmap.h @@ -8,7 +8,7 @@ #include #include -namespace kraken::binding::qjs { +namespace kraken { template class HeapHashMap { @@ -17,86 +17,86 @@ class HeapHashMap { explicit HeapHashMap(JSContext* ctx); ~HeapHashMap(); - bool contains(K key); - JSValue getProperty(K key); - void setProperty(K key, JSValue value); - void copyWith(HeapHashMap* newValue); - void erase(K key); + bool Contains(K key); + JSValue GetProperty(K key); + void SetProperty(K key, JSValue value); + void CopyWith(HeapHashMap* newValue); + void Erase(K key); - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const; + void Trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const; private: - JSRuntime* m_runtime{nullptr}; - JSContext* m_ctx{nullptr}; - std::unordered_map m_entries; + JSRuntime* runtime_{nullptr}; + JSContext* ctx_{nullptr}; + std::unordered_map entries_; }; template -HeapHashMap::HeapHashMap(JSContext* ctx) : m_runtime(JS_GetRuntime(ctx)), m_ctx(ctx) {} +HeapHashMap::HeapHashMap(JSContext* ctx) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx) {} template HeapHashMap::~HeapHashMap() { - for (auto& entry : m_entries) { - JS_FreeAtomRT(m_runtime, entry.first); - JS_FreeValueRT(m_runtime, entry.second); + for (auto& entry : entries_) { + JS_FreeAtomRT(runtime_, entry.first); + JS_FreeValueRT(runtime_, entry.second); } } template -bool HeapHashMap::contains(K key) { - return m_entries.count(key) > 0; +bool HeapHashMap::Contains(K key) { + return entries_.count(key) > 0; } template -JSValue HeapHashMap::getProperty(K key) { - if (m_entries.count(key) == 0) +JSValue HeapHashMap::GetProperty(K key) { + if (entries_.count(key) == 0) return JS_NULL; - return m_entries[key]; + return entries_[key]; } template -void HeapHashMap::setProperty(K key, JSValue value) { +void HeapHashMap::SetProperty(K key, JSValue value) { // GC can't track the value if key had been override. // Should free the value if exist on m_properties. - if (m_entries.count(key) > 0) { - JS_FreeAtom(m_ctx, key); - JS_FreeValue(m_ctx, m_entries[key]); + if (entries_.count(key) > 0) { + JS_FreeAtom(ctx_, key); + JS_FreeValue(ctx_, entries_[key]); } - m_entries[key] = value; + entries_[key] = value; } template -void HeapHashMap::copyWith(HeapHashMap* newValue) { - for (auto& entry : m_entries) { +void HeapHashMap::CopyWith(HeapHashMap* newValue) { + for (auto& entry : entries_) { // We should also dup atom if K is JSAtom. if (std::is_same::value) { - JS_DupAtom(m_ctx, entry.first); + JS_DupAtom(ctx_, entry.first); } - newValue->m_entries[entry.first] = JS_DupValue(m_ctx, entry.second); + newValue->entries_[entry.first] = JS_DupValue(ctx_, entry.second); } } template -void HeapHashMap::erase(K key) { - if (m_entries.count(key) == 0) +void HeapHashMap::Erase(K key) { + if (entries_.count(key) == 0) return; // We should also free atom if K is JSAtom. if (std::is_same::value) { - JS_FreeAtomRT(m_runtime, key); + JS_FreeAtomRT(runtime_, key); } - JS_FreeValueRT(m_runtime, m_entries[key]); - m_entries.erase(key); + JS_FreeValueRT(runtime_, entries_[key]); + entries_.erase(key); } template -void HeapHashMap::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - for (auto& entry : m_entries) { +void HeapHashMap::Trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + for (auto& entry : entries_) { JS_MarkValue(rt, entry.second, mark_func); } } -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_HEAP_HASHMAP_H_ diff --git a/bridge/bindings/qjs/host_class.h b/bridge/bindings/qjs/host_class.h deleted file mode 100644 index 6e05a7950a..0000000000 --- a/bridge/bindings/qjs/host_class.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_HOST_CLASS_H -#define KRAKENBRIDGE_HOST_CLASS_H - -#include "executing_context.h" -#include "qjs_patch.h" -#include "third_party/quickjs/quickjs.h" - -namespace kraken::binding::qjs { - -class Instance; - -class HostClass { - public: - KRAKEN_DISALLOW_COPY_AND_ASSIGN(HostClass); - - HostClass(ExecutionContext* context, std::string name) : m_context(context), m_name(std::move(name)), m_ctx(context->ctx()), m_contextId(context->getContextId()) { - /// JavaScript object in QuickJS are created by template, in QuickJS, these template is called JSClassDef. - /// JSClassDef define this JSObject's base behavior like className, property getter and setter, and advanced feature such as run a callback when JSObject had been freed by QuickJS garbage - /// collector. Every JSClassDef must have a unique ID, called JSClassID, you can obtain this ID from JS_NewClassID() API. If your wants to create JSObjects defined by your own template, please - /// follow this steps: - /// 1. Use JS_NewClassID() to allocate new id for your template. - /// 2. Create JSClassDef and set up your customized behavior about your JSObject. - /// 3. Use JS_NewClass() to initialize your template and you can use your unique JSClassID to create JSObjects. - /// 4. Use JS_NewObjectClass() to create your JSObjects. - /// Example: - /// JSClassID sampleId; - /// JS_NewClassID(&sampleId); - /// - /// JSClassDef def{}; - /// def.class_name = "SampleClass"; - /// def.finalizer = [](JSRuntime* rt, JSValue val) { - /// // Do something when jsObject been freed by GC - /// }; - /// def.call = [](JSContext * ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { - /// // Do something when jsObject been called as function or called as constructor. - /// }; - /// JS_NewClass(runtime, sampleId, &def); - /// JSValue jsObject = JS_NewObjectClass(ctx, sampleId); - JSClassDef def{}; - def.class_name = "HostClass"; - def.finalizer = proxyFinalize; - def.call = proxyCall; - JS_NewClass(context->runtime(), ExecutionContext::kHostClassClassId, &def); - jsObject = JS_NewObjectClass(context->ctx(), ExecutionContext::kHostClassClassId); - m_prototypeObject = JS_NewObject(m_ctx); - - // Make constructor function inherit to Function.prototype - JSValue functionConstructor = JS_GetPropertyStr(m_ctx, m_context->global(), "Function"); - JSValue functionPrototype = JS_GetPropertyStr(m_ctx, functionConstructor, "prototype"); - JS_SetPrototype(m_ctx, jsObject, functionPrototype); - JS_FreeValue(m_ctx, functionPrototype); - JS_FreeValue(m_ctx, functionConstructor); - - JSAtom prototypeKey = JS_NewAtom(m_ctx, "prototype"); - JS_DefinePropertyValue(m_ctx, jsObject, prototypeKey, m_prototypeObject, JS_PROP_C_W_E); - JS_FreeAtom(m_ctx, prototypeKey); - - JS_SetConstructorBit(m_ctx, jsObject, true); - JS_SetOpaque(jsObject, this); - }; - virtual ~HostClass() = default; - - virtual JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValueConst* argv) { return JS_NewObject(ctx); }; - JSValue jsObject; - - inline uint32_t contextId() const { return m_contextId; } - inline ExecutionContext* context() const { return m_context; } - inline JSValue prototype() const { return m_prototypeObject; }; - - protected: - JSValue m_prototypeObject{JS_NULL}; - std::string m_name; - ExecutionContext* m_context; - int32_t m_contextId; - JSContext* m_ctx; - - private: - friend Instance; - static void proxyFinalize(JSRuntime* rt, JSValue val) { - auto hostObject = static_cast(JS_GetOpaque(val, ExecutionContext::kHostClassClassId)); - delete hostObject; - }; - static JSValue proxyCall(JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) { - // This jsObject is called as a constructor. - if ((flags & JS_CALL_FLAG_CONSTRUCTOR) != 0) { - auto* hostClass = static_cast(JS_GetOpaque(func_obj, ExecutionContext::kHostClassClassId)); - JSValue instance = hostClass->instanceConstructor(ctx, func_obj, this_val, argc, argv); - JSValue proto = JS_GetPropertyStr(ctx, this_val, "prototype"); - JS_SetPrototype(ctx, instance, proto); - JS_FreeValue(ctx, proto); - return instance; - } - - return this_val; - } -}; - -class Instance { - public: - explicit Instance(HostClass* hostClass, std::string name, JSClassExoticMethods* exotic, JSClassID classId, JSClassFinalizer finalizer) - : m_context(hostClass->context()), m_hostClass(hostClass), m_name(std::move(name)), m_ctx(m_context->ctx()), m_contextId(hostClass->contextId()) { - JSClassDef def{}; - def.class_name = m_name.c_str(); - def.finalizer = finalizer; - def.exotic = exotic; - def.gc_mark = proxyGCMark; - int32_t success = JS_NewClass(m_context->runtime(), classId, &def); - jsObject = JS_NewObjectProtoClass(m_ctx, hostClass->m_prototypeObject, classId); - JS_SetOpaque(jsObject, this); - }; - JSValue jsObject; - virtual ~Instance() = default; - - inline HostClass* prototype() const { return m_hostClass; } - inline ExecutionContext* context() const { return m_context; } - inline std::string name() const { return m_name; } - - private: - static void proxyGCMark(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) { - auto* instance = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); - instance->trace(rt, val, mark_func); - } - - protected: - // Subclass must to provider a method of void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) - // to tell GC all JSValues are managed by them. - virtual void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func){}; - - ExecutionContext* m_context{nullptr}; - JSContext* m_ctx{nullptr}; - HostClass* m_hostClass{nullptr}; - std::string m_name; - int64_t m_contextId{-1}; - - friend HostClass; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_HOST_CLASS_H diff --git a/bridge/bindings/qjs/host_class_test.cc b/bridge/bindings/qjs/host_class_test.cc deleted file mode 100644 index 00cd87cba7..0000000000 --- a/bridge/bindings/qjs/host_class_test.cc +++ /dev/null @@ -1,511 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "host_class.h" -#include -#include "gtest/gtest.h" -#include "kraken_test_env.h" -#include "page.h" - -namespace kraken::binding::qjs { - -class ParentClass : public HostClass { - public: - explicit ParentClass(ExecutionContext* context) : HostClass(context, "ParentClass") {} - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValueConst* argv) override { return HostClass::instanceConstructor(ctx, func_obj, this_val, argc, argv); } - - OBJECT_INSTANCE(ParentClass); - - static JSValue foo(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, 20); } - - private: - ObjectFunction m_foo{m_context, m_prototypeObject, "foo", foo, 0}; -}; - -class SampleClass; -static JSClassID kSampleClassId{0}; - -class SampleClassInstance : public Instance { - public: - explicit SampleClassInstance(HostClass* sampleClass) : Instance(sampleClass, "SampleClass", nullptr, kSampleClassId, finalizer){}; - - private: - static void finalizer(JSRuntime* rt, JSValue v) { - auto* instance = static_cast(JS_GetOpaque(v, kSampleClassId)); - if (instance->context()->isValid()) { - JS_FreeValue(instance->m_ctx, instance->jsObject); - } - delete instance; - } -}; - -std::once_flag kSampleClassOnceFlag; -class SampleClass : public ParentClass { - public: - explicit SampleClass(ExecutionContext* context) : ParentClass(context) { - std::call_once(kSampleClassOnceFlag, []() { JS_NewClassID(&kSampleClassId); }); - JS_SetPrototype(m_ctx, m_prototypeObject, ParentClass::instance(m_context)->prototype()); - } - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override { - auto* sampleClass = static_cast(JS_GetOpaque(func_obj, ExecutionContext::kHostClassClassId)); - auto* instance = new SampleClassInstance(sampleClass); - return instance->jsObject; - } - ~SampleClass() {} - - private: - static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, 10); } - - ObjectFunction m_f{m_context, m_prototypeObject, "f", f, 0}; -}; - -TEST(HostClass, newInstance) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "10"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - auto* sampleObject = new SampleClass(context); - auto* parentObject = ParentClass::instance(context); - context->defineGlobalProperty("SampleClass", sampleObject->jsObject); - context->defineGlobalProperty("ParentClass", parentObject->jsObject); - const char* code = "let obj = new SampleClass(1,2,3,4); console.log(obj.f())"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, instanceOf) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "true"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - errorCalled = true; - KRAKEN_LOG(VERBOSE) << errmsg; - }); - auto context = bridge->getContext(); - auto* sampleObject = new SampleClass(context); - auto* parentObject = ParentClass::instance(context); - // Test for C API - context->defineGlobalProperty("SampleClass", sampleObject->jsObject); - context->defineGlobalProperty("ParentClass", parentObject->jsObject); - JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, parentObject->jsObject); - EXPECT_EQ(isInstanceof, true); - JS_FreeValue(context->ctx(), object); - - // Test with Javascript - const char* code = "let obj = new SampleClass(1,2,3,4); \n console.log(obj instanceof SampleClass)"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, inheritance) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "20"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - errorCalled = true; - KRAKEN_LOG(VERBOSE) << errmsg; - }); - auto context = bridge->getContext(); - auto* sampleObject = new SampleClass(context); - - auto* parentObject = ParentClass::instance(context); - context->defineGlobalProperty("ParentClass", parentObject->jsObject); - - context->defineGlobalProperty("SampleClass", sampleObject->jsObject); - - const char* code = - "let obj = new SampleClass(1,2,3,4);\n" - "console.log(obj.foo())"; - context->evaluateJavaScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, inherintanceInJavaScript) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "TEST 10 20"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - errorCalled = true; - KRAKEN_LOG(VERBOSE) << errmsg; - }); - auto context = bridge->getContext(); - auto* sampleObject = new SampleClass(context); - - auto* parentObject = ParentClass::instance(context); - context->defineGlobalProperty("ParentClass", parentObject->jsObject); - - context->defineGlobalProperty("SampleClass", sampleObject->jsObject); - - const char* code = R"( -class Demo extends SampleClass { - constructor(name) { - super(); - this.name = name; - } - - getName() { - return this.name.toUpperCase(); - } -} -let demo = new Demo('test'); -console.log(demo.getName(), demo.f(), demo.foo()); -)"; - context->evaluateJavaScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, haveFunctionProtoMethods) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "ƒ ()"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - errorCalled = true; - KRAKEN_LOG(VERBOSE) << errmsg; - }); - auto context = bridge->getContext(); - auto* parentObject = ParentClass::instance(context); - context->defineGlobalProperty("ParentClass", parentObject->jsObject); - - const char* code = R"( -class Demo extends ParentClass { - constructor(name) { - super(); - this.name = name; - } - - getName() { - return this.name.toUpperCase(); - } -} -console.log(Demo.call); -)"; - context->evaluateJavaScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, multipleInstance) { - bool static errorCalled = false; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - errorCalled = true; - KRAKEN_LOG(VERBOSE) << errmsg; - }); - auto context = bridge->getContext(); - - auto* parentObject = ParentClass::instance(context); - context->defineGlobalProperty("ParentClass", parentObject->jsObject); - - // Test for C API 1 - { - auto* sampleObject = new SampleClass(context); - context->defineGlobalProperty("SampleClass1", sampleObject->jsObject); - JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); - EXPECT_EQ(isInstanceof, true); - JS_FreeValue(context->ctx(), object); - } - - // Test for C API 2 - { - auto* sampleObject = new SampleClass(context); - context->defineGlobalProperty("SampleClass2", sampleObject->jsObject); - JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); - EXPECT_EQ(isInstanceof, true); - JS_FreeValue(context->ctx(), object); - } - - { - auto* sampleObject = new SampleClass(context); - context->defineGlobalProperty("SampleClass3", sampleObject->jsObject); - JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); - EXPECT_EQ(isInstanceof, true); - JS_FreeValue(context->ctx(), object); - } - - { - auto* sampleObject = new SampleClass(context); - context->defineGlobalProperty("SampleClass4", sampleObject->jsObject); - JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); - EXPECT_EQ(isInstanceof, true); - JS_FreeValue(context->ctx(), object); - } - - EXPECT_EQ(errorCalled, false); -} - -std::once_flag kExoticClassOnceFlag; - -class ExoticClassInstance; -class ExoticClass : public HostClass { - public: - static JSClassID exoticClassID; - ExoticClass() = delete; - explicit ExoticClass(ExecutionContext* context) : HostClass(context, "ExoticClass") { - std::call_once(kExoticClassOnceFlag, []() { JS_NewClassID(&exoticClassID); }); - } - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv); - - private: - friend ExoticClassInstance; -}; - -JSClassID ExoticClass::exoticClassID{0}; -static bool exoticClassFreed = false; - -class ExoticClassInstance : public Instance { - public: - ExoticClassInstance() = delete; - static JSClassExoticMethods methods; - - explicit ExoticClassInstance(ExoticClass* exoticClass) : Instance(exoticClass, "ExoticClass", &methods, ExoticClass::exoticClassID, finalizer){}; - - static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { - auto* instance = static_cast(JS_GetOpaque(obj, ExoticClass::exoticClassID)); - auto* prototype = static_cast(instance->prototype()); - if (JS_HasProperty(ctx, prototype->m_prototypeObject, atom)) { - return JS_GetProperty(ctx, prototype->m_prototypeObject, atom); - } - - if (instance->m_properties.count(atom) > 0) { - return instance->m_properties[atom]; - } - - return JS_NULL; - }; - - static void finalizer(JSRuntime* rt, JSValue val) { - auto* instance = static_cast(JS_GetOpaque(val, ExoticClass::exoticClassID)); - if (instance->context()->isValid()) { - JS_FreeValue(instance->m_ctx, instance->jsObject); - } - delete instance; - }; - - static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { - auto* instance = static_cast(JS_GetOpaque(obj, ExoticClass::exoticClassID)); - instance->m_properties[atom] = JS_DupValue(ctx, value); - return 0; - } - ~ExoticClassInstance() { exoticClassFreed = true; } - friend ExoticClass; - - class ClassNamePropertyDescriptor { - public: - static JSValue getter(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* instance = static_cast(JS_GetOpaque(this_val, ExoticClass::exoticClassID)); - return JS_NewFloat64(ctx, instance->classValue); - }; - static JSValue setter(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* instance = static_cast(JS_GetOpaque(this_val, ExoticClass::exoticClassID)); - double v; - JS_ToFloat64(ctx, &v, argv[0]); - instance->classValue = v; - return JS_NULL; - }; - }; - ObjectProperty m_getClassName{m_context, jsObject, "className", ClassNamePropertyDescriptor::getter, ClassNamePropertyDescriptor::setter}; - - private: - std::unordered_map m_properties; - double classValue{100.0}; -}; - -JSClassExoticMethods ExoticClassInstance::methods{nullptr, nullptr, nullptr, nullptr, nullptr, getProperty, setProperty}; - -JSValue ExoticClass::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - return (new ExoticClassInstance(this))->jsObject; -} - -TEST(HostClass, exoticClass) { - bool static errorCalled = false; - bool static logCalled = false; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "10"); - }; - - auto context = bridge->getContext(); - auto* constructor = new ExoticClass(context); - context->defineGlobalProperty("ExoticClass", constructor->jsObject); - - std::string code = - "globalThis.obj = new ExoticClass();" - "var key = 'onclick'; " - "var otherKey = 'o' + 'n' + 'c' + 'l' + 'i' + 'c' + 'k';" - "obj[key] = function() {return 10;};" - "console.log(obj[otherKey]());"; - context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, setExoticClassProperty) { - bool static errorCalled = false; - bool static logCalled = false; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "200"); - }; - - auto context = bridge->getContext(); - auto* constructor = new ExoticClass(context); - context->defineGlobalProperty("ExoticClass", constructor->jsObject); - - std::string code = - "var obj = new ExoticClass();" - "obj.className = 200.0;" - "console.log(obj.className);"; - context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(exoticClassFreed, true); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, finalizeShouldNotFree) { - bool static errorCalled = false; - bool static logCalled = false; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; - - auto context = bridge->getContext(); - auto* constructor = new ExoticClass(context); - context->defineGlobalProperty("ExoticClass", constructor->jsObject); - - auto runGC = [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { - JS_RunGC(JS_GetRuntime(ctx)); - return JS_NULL; - }; - QJS_GLOBAL_BINDING_FUNCTION(context, runGC, "__kraken_run_gc__", 1); - - std::string code = R"( -function throttle(func, wait) { - var ctx; - var args; - var rtn; - var timeoutID; - var last = 0; - - function call() { - timeoutID = 0; - last = +new Date(); - rtn = func.apply(ctx, args); - ctx = null; - // args = null; - } - - return function () { - ctx = this; - args = arguments; - var delta = new Date().getTime() - last; - if (!timeoutID) if (delta >= wait) call();else timeoutID = setTimeout(call, wait - delta); - return rtn; - }; -} - -var handleScroll = function (e) { -}; - -{ - let div; - function initScroll() { - div = document.createElement('div'); - div.style.width = '100px'; - div.style.height = '300px'; - div.style.overflow = 'scroll'; - div.addEventListener('scroll', throttle(handleScroll, 100)); - document.body.appendChild(div); - for(let i = 0; i < 1000; i ++) { - div.appendChild(document.createTextNode('abc')); - } - } - - function triggerScroll() { - let scrollEvent = new CustomEvent('scroll'); - div.dispatchEvent(scrollEvent); - } - - initScroll(); - window.onclick = () => { - document.body.removeChild(div) - initScroll(); - }; - window.addEventListener('trigger', () => { - triggerScroll(); - }); -} - -)"; - context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); - - static auto* window = static_cast(JS_GetOpaque(context->global(), 1)); - - auto triggerScrollEventAndLoopTimer = [&context]() { - TEST_dispatchEvent(context->getContextId(), window, "trigger"); - TEST_runLoop(context); - }; - - triggerScrollEventAndLoopTimer(); - triggerScrollEventAndLoopTimer(); - triggerScrollEventAndLoopTimer(); - - TEST_dispatchEvent(context->getContextId(), window, "click"); - - triggerScrollEventAndLoopTimer(); - triggerScrollEventAndLoopTimer(); - triggerScrollEventAndLoopTimer(); - - TEST_dispatchEvent(context->getContextId(), window, "click"); - - triggerScrollEventAndLoopTimer(); - triggerScrollEventAndLoopTimer(); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/host_object.cc b/bridge/bindings/qjs/host_object.cc deleted file mode 100644 index 9bf1a5b662..0000000000 --- a/bridge/bindings/qjs/host_object.cc +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "host_object.h" - -namespace kraken::binding::qjs { - -JSValue ExoticHostObject::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { - return JS_NULL; -} -int ExoticHostObject::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { - return 0; -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/host_object.h b/bridge/bindings/qjs/host_object.h deleted file mode 100644 index f69086f385..0000000000 --- a/bridge/bindings/qjs/host_object.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_HOST_OBJECT_H -#define KRAKENBRIDGE_HOST_OBJECT_H - -#include "executing_context.h" - -namespace kraken::binding::qjs { - -class HostObject { - public: - KRAKEN_DISALLOW_COPY_AND_ASSIGN(HostObject); - - HostObject() = delete; - HostObject(ExecutionContext* context, std::string name) : m_context(context), m_name(std::move(name)), m_ctx(context->ctx()), m_contextId(context->getContextId()) { - JSClassDef def{}; - def.class_name = "HostObject"; - def.finalizer = proxyFinalize; - JS_NewClass(context->runtime(), ExecutionContext::kHostObjectClassId, &def); - jsObject = JS_NewObjectClass(m_ctx, ExecutionContext::kHostObjectClassId); - JS_SetOpaque(jsObject, this); - } - - JSValue jsObject{JS_NULL}; - - protected: - virtual ~HostObject() = default; - std::string m_name; - ExecutionContext* m_context; - int32_t m_contextId; - JSContext* m_ctx; - - private: - static void proxyFinalize(JSRuntime* rt, JSValue val) { - auto hostObject = static_cast(JS_GetOpaque(val, ExecutionContext::kHostObjectClassId)); - delete hostObject; - }; -}; - -class ExoticHostObject { - public: - KRAKEN_DISALLOW_COPY_AND_ASSIGN(ExoticHostObject); - - ExoticHostObject() = delete; - ExoticHostObject(ExecutionContext* context, std::string name) : m_context(context), m_name(std::move(name)), m_ctx(context->ctx()), m_contextId(context->getContextId()) { - JSClassExoticMethods* m_exoticMethods = new JSClassExoticMethods{nullptr, nullptr, nullptr, nullptr, nullptr, proxyGetProperty, proxySetProperty}; - JSClassDef def{}; - def.class_name = m_name.c_str(); - def.finalizer = proxyFinalize; - def.exotic = m_exoticMethods; - JS_NewClass(context->runtime(), ExecutionContext::kHostExoticObjectClassId, &def); - jsObject = JS_NewObjectClass(m_ctx, ExecutionContext::kHostExoticObjectClassId); - JS_SetOpaque(jsObject, this); - } - - JSValue jsObject{JS_NULL}; - - static JSValue proxyGetProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { - auto* object = static_cast(JS_GetOpaque(obj, ExecutionContext::kHostExoticObjectClassId)); - return object->getProperty(ctx, obj, atom, receiver); - }; - static int proxySetProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { - auto* object = static_cast(JS_GetOpaque(obj, ExecutionContext::kHostExoticObjectClassId)); - return object->setProperty(ctx, obj, atom, value, receiver, flags); - }; - - virtual JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); - virtual int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); - - protected: - virtual ~ExoticHostObject() = default; - std::string m_name; - ExecutionContext* m_context; - int32_t m_contextId; - JSContext* m_ctx; - - static void proxyFinalize(JSRuntime* rt, JSValue val) { - auto hostObject = static_cast(JS_GetOpaque(val, ExecutionContext::kHostExoticObjectClassId)); - delete hostObject; - }; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_HOST_OBJECT_H diff --git a/bridge/bindings/qjs/host_object_test.cc b/bridge/bindings/qjs/host_object_test.cc deleted file mode 100644 index c02bb8046d..0000000000 --- a/bridge/bindings/qjs/host_object_test.cc +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "host_object.h" -#include -#include "executing_context.h" -#include "kraken_test_env.h" -#include "page.h" - -namespace kraken::binding::qjs { - -static bool isSampleFree = false; - -class SampleObject : public HostObject { - public: - explicit SampleObject(ExecutionContext* context) : HostObject(context, "SampleObject"){}; - ~SampleObject() { isSampleFree = true; } - - private: - class FooPropertyDescriptor { - public: - static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* sampleObject = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, sampleObject->m_foo); - } - static JSValue setter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* sampleObject = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - double f; - JS_ToFloat64(ctx, &f, argv[0]); - sampleObject->m_foo = f; - return JS_NULL; - } - }; - - static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - double v; - JS_ToFloat64(ctx, &v, argv[0]); - return JS_NewFloat64(ctx, 10 + v); - } - - double m_foo{0}; - ObjectProperty m_width{m_context, jsObject, "foo", FooPropertyDescriptor::getter, FooPropertyDescriptor::setter}; - ObjectFunction m_f{m_context, jsObject, "f", f, 1}; -}; - -TEST(HostObject, defineProperty) { - bool static logCalled = false; - bool static errorCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - - EXPECT_STREQ(message.c_str(), "{f: ƒ (), foo: 1}"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); - auto* sampleObject = new SampleObject(context); - JSValue object = sampleObject->jsObject; - context->defineGlobalProperty("o", object); - const char* code = "o.foo++; console.log(o);"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(logCalled, true); - EXPECT_EQ(errorCalled, false); -} - -TEST(ObjectProperty, worksWithProxy) { - bool static logCalled = false; - bool static errorCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "0"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - auto* sampleObject = new SampleObject(context); - JSValue object = sampleObject->jsObject; - context->defineGlobalProperty("o", object); - std::string code = std::string(R"( -let p = new Proxy(o, { - get(target, key, receiver) { - return Reflect.get(target, key, receiver); - } -}); -console.log(p.foo); -)"); - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - - EXPECT_EQ(logCalled, true); - EXPECT_EQ(errorCalled, false); -} - -TEST(HostObject, defineFunction) { - bool static logCalled = false; - bool static errorCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "20"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - auto* sampleObject = new SampleObject(context); - JSValue object = sampleObject->jsObject; - context->defineGlobalProperty("o", object); - const char* code = "console.log(o.f(10))"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(logCalled, true); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(isSampleFree, true); -} - -class SampleExoticHostObject : public ExoticHostObject { - public: - explicit SampleExoticHostObject(ExecutionContext* context) : ExoticHostObject(context, "SampleObject"){}; - ~SampleExoticHostObject() { isSampleFree = true; } - - JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); - int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); - - private: -}; - -JSValue SampleExoticHostObject::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { - return JS_NewFloat64(ctx, 100.0); -} -int SampleExoticHostObject::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { - return 0; -} - -TEST(ExoticHostObject, overriteGetterSetter) { - bool static logCalled = false; - bool static errorCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "100"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - auto* sampleObject = new SampleExoticHostObject(context); - JSValue object = sampleObject->jsObject; - context->defineGlobalProperty("o", object); - const char* code = "console.log(o.abc)"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(logCalled, true); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(isSampleFree, true); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/html_parser.cc b/bridge/bindings/qjs/html_parser.cc deleted file mode 100644 index fd85d0625c..0000000000 --- a/bridge/bindings/qjs/html_parser.cc +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "html_parser.h" -#include "dom/document.h" -#include "dom/text_node.h" -#include "executing_context.h" -#include "foundation/logging.h" - -#include - -namespace kraken::binding::qjs { - -inline std::string trim(std::string& str) { - str.erase(0, str.find_first_not_of(' ')); // prefixing spaces - str.erase(str.find_last_not_of(' ') + 1); // surfixing spaces - return str; -} - -// Parse html,isHTMLFragment should be false if need to automatically complete html, head, and body when they are missing. -GumboOutput* parse(std::string& html, bool isHTMLFragment = false) { - // Gumbo-parser parse HTML. - GumboOutput* htmlTree = gumbo_parse_with_options(&kGumboDefaultOptions, html.c_str(), html.length()); - - if (isHTMLFragment) { - // Find body. - const GumboVector* children = &htmlTree->root->v.element.children; - for (int i = 0; i < children->length; ++i) { - auto* child = (GumboNode*)children->data[i]; - if (child->type == GUMBO_NODE_ELEMENT) { - std::string tagName; - if (child->v.element.tag != GUMBO_TAG_UNKNOWN) { - tagName = gumbo_normalized_tagname(child->v.element.tag); - } else { - GumboStringPiece piece = child->v.element.original_tag; - gumbo_tag_from_original_text(&piece); - tagName = std::string(piece.data, piece.length); - } - - if (tagName.compare("body") == 0) { - htmlTree->root = child; - break; - } - } - } - } - - return htmlTree; -} - -void HTMLParser::traverseHTML(NodeInstance* root, GumboNode* node) { - ExecutionContext* context = root->context(); - JSContext* ctx = context->ctx(); - - const GumboVector* children = &node->v.element.children; - for (int i = 0; i < children->length; ++i) { - auto* child = (GumboNode*)children->data[i]; - - if (child->type == GUMBO_NODE_ELEMENT) { - std::string tagName; - if (child->v.element.tag != GUMBO_TAG_UNKNOWN) { - tagName = gumbo_normalized_tagname(child->v.element.tag); - } else { - GumboStringPiece piece = child->v.element.original_tag; - gumbo_tag_from_original_text(&piece); - tagName = std::string(piece.data, piece.length); - } - - auto* Document = Document::instance(context); - JSValue constructor = Document->getElementConstructor(context, tagName); - - JSValue tagNameValue = JS_NewString(ctx, tagName.c_str()); - JSValue argv[] = {tagNameValue}; - JSValue newElementValue = JS_CallConstructor(ctx, constructor, 1, argv); - JS_FreeValue(ctx, tagNameValue); - auto* newElementInstance = static_cast(JS_GetOpaque(newElementValue, Element::classId())); - root->internalAppendChild(newElementInstance); - parseProperty(newElementInstance, &child->v.element); - - // eval javascript when . - if (child->v.element.children.length > 0) { - if (child->v.element.tag == GUMBO_TAG_SCRIPT) { - const char* code = ((GumboNode*)child->v.element.children.data[0])->v.text.text; - context->evaluateJavaScript(code, strlen(code), "vm://", 0); - } else { - traverseHTML(newElementInstance, child); - } - } - - JS_FreeValue(ctx, newElementValue); - } else if (child->type == GUMBO_NODE_TEXT) { - JSValue textContentValue = JS_NewString(ctx, child->v.text.text); - JSValue argv[] = {textContentValue}; - JSValue textNodeValue = JS_CallConstructor(ctx, TextNode::instance(context)->jsObject, 1, argv); - JS_FreeValue(ctx, textContentValue); - - auto* textNodeInstance = static_cast(JS_GetOpaque(textNodeValue, TextNode::classId())); - root->internalAppendChild(textNodeInstance); - JS_FreeValue(ctx, textNodeValue); - } - } -} - -bool HTMLParser::parseHTML(std::string html, NodeInstance* rootNode, bool isHTMLFragment) { - if (rootNode != nullptr) { - rootNode->internalClearChild(); - - if (!trim(html).empty()) { - GumboOutput* htmlTree = parse(html, isHTMLFragment); - - traverseHTML(rootNode, htmlTree->root); - // Free gumbo parse nodes. - gumbo_destroy_output(&kGumboDefaultOptions, htmlTree); - } - } else { - KRAKEN_LOG(ERROR) << "Root node is null."; - } - - return true; -} - -bool HTMLParser::parseHTML(std::string html, NodeInstance* rootNode) { - return parseHTML(html, rootNode, false); -} - -bool HTMLParser::parseHTML(const char* code, size_t codeLength, NodeInstance* rootNode) { - std::string html = std::string(code, codeLength); - return parseHTML(html, rootNode, false); -} - -bool HTMLParser::parseHTMLFragment(const char* code, size_t codeLength, NodeInstance* rootNode) { - std::string html = std::string(code, codeLength); - return parseHTML(html, rootNode, true); -} - -void HTMLParser::parseProperty(ElementInstance* element, GumboElement* gumboElement) { - ExecutionContext* context = element->context(); - JSContext* ctx = context->ctx(); - - GumboVector* attributes = &gumboElement->attributes; - for (int j = 0; j < attributes->length; ++j) { - auto* attribute = (GumboAttribute*)attributes->data[j]; - - if (strcmp(attribute->name, "style") == 0) { - std::vector arrStyles; - std::string::size_type prev_pos = 0, pos = 0; - std::string strStyles = attribute->value; - - while ((pos = strStyles.find(';', pos)) != std::string::npos) { - arrStyles.push_back(strStyles.substr(prev_pos, pos - prev_pos)); - prev_pos = ++pos; - } - arrStyles.push_back(strStyles.substr(prev_pos, pos - prev_pos)); - - auto* style = element->style(); - - for (auto& s : arrStyles) { - std::string::size_type position = s.find(':'); - if (position != std::basic_string::npos) { - std::string styleKey = s.substr(0, position); - trim(styleKey); - - std::string styleValue = s.substr(position + 1, s.length()); - trim(styleValue); - - JSValue newStyleValue = JS_NewString(ctx, styleValue.c_str()); - style->internalSetProperty(styleKey, newStyleValue); - JS_FreeValue(ctx, newStyleValue); - } - } - - } else { - std::string strName = attribute->name; - std::string strValue = attribute->value; - - JSValue key = JS_NewString(ctx, strName.c_str()); - JSValue value = JS_NewString(ctx, strValue.c_str()); - - JSValue setAttributeFunc = JS_GetPropertyStr(ctx, element->jsObject, "setAttribute"); - JSValue arguments[] = {key, value}; - - JSValue returnValue = JS_Call(ctx, setAttributeFunc, element->jsObject, 2, arguments); - context->drainPendingPromiseJobs(); - context->handleException(&returnValue); - - JS_FreeValue(ctx, setAttributeFunc); - JS_FreeValue(ctx, key); - JS_FreeValue(ctx, value); - } - } -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/html_parser.h b/bridge/bindings/qjs/html_parser.h deleted file mode 100644 index 0b3f32e428..0000000000 --- a/bridge/bindings/qjs/html_parser.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_HTML_PARSER_H -#define KRAKENBRIDGE_HTML_PARSER_H - -#include "bindings/qjs/dom/element.h" -#include "executing_context.h" -#include "include/kraken_bridge.h" -#include "third_party/gumbo-parser/src/gumbo.h" - -namespace kraken::binding::qjs { - -class HTMLParser { - public: - static bool parseHTML(const char* code, size_t codeLength, NodeInstance* rootNode); - static bool parseHTML(std::string html, NodeInstance* rootNode); - static bool parseHTMLFragment(const char* code, size_t codeLength, NodeInstance* rootNode); - - private: - ExecutionContext* m_context; - static void traverseHTML(NodeInstance* root, GumboNode* node); - static void parseProperty(ElementInstance* element, GumboElement* gumboElement); - - static bool parseHTML(std::string html, NodeInstance* rootNode, bool isHTMLFragment); -}; -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_HTML_PARSER_H diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h new file mode 100644 index 0000000000..4f20bc1afb --- /dev/null +++ b/bridge/bindings/qjs/idl_type.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ + +#include +#include "converter.h" + +namespace kraken { + +struct IDLTypeBase { + using ImplType = void; +}; + +template +struct IDLTypeBaseHelper { + using ImplType = T; +}; + +class ScriptValue; +// Any +struct IDLAny final : public IDLTypeBaseHelper {}; + +template +struct IDLOptional final : public IDLTypeBase { + using ImplType = typename Converter::ImplType; +}; + +// Nullable +template +struct IDLNullable final : public IDLTypeBase { + using ImplType = typename Converter::ImplType; +}; + +// Bool +struct IDLBoolean final : public IDLTypeBaseHelper {}; + +// Primitive types +struct IDLInt32 final : public IDLTypeBaseHelper {}; +struct IDLInt64 final : public IDLTypeBaseHelper {}; +struct IDLUint32 final : public IDLTypeBaseHelper {}; +struct IDLDouble final : public IDLTypeBaseHelper {}; + +class NativeString; +// DOMString is UTF-16 strings. +// https://stackoverflow.com/questions/35123890/what-is-a-domstring-really +struct IDLDOMString final : public IDLTypeBaseHelper {}; + +// https://developer.mozilla.org/en-US/docs/Web/API/USVString +struct IDLUSVString final : public IDLTypeBaseHelper {}; + +// Object +struct IDLObject : public IDLTypeBaseHelper {}; + +class QJSFunction; +// Function callback +struct IDLCallback : public IDLTypeBaseHelper> { + using ImplType = typename Converter>::ImplType; +}; + +// Sequence +template +struct IDLSequence final : public IDLTypeBase { + using ImplType = typename std::vector; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ diff --git a/bridge/bindings/qjs/js_based_event_listener.cc b/bridge/bindings/qjs/js_based_event_listener.cc new file mode 100644 index 0000000000..a15b9242b7 --- /dev/null +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "js_based_event_listener.h" + +namespace kraken { + +// Implements step 2. of "inner invoke". +// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke +void JSBasedEventListener::Invoke(ExecutingContext* context, Event* event, ExceptionState& exception_state) { + assert(context); + assert(event); + + if (!context->IsValid()) + return; + // Step 10: Call a listener with event's currentTarget as receiver and event + // and handle errors if thrown. + InvokeInternal(*event->currentTarget(), *event, exception_state); +} + +JSBasedEventListener::JSBasedEventListener() {} + +} // namespace kraken diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h new file mode 100644 index 0000000000..5674320164 --- /dev/null +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ + +#include +#include "core/dom/events/event_listener.h" +#include "core/executing_context.h" + +namespace kraken { + +// |JSBasedEventListener| is the base class for JS-based event listeners, +// i.e. EventListener and EventHandler in the standards. +// This provides the essential APIs of JS-based event listeners and also +// implements the common features. +class JSBasedEventListener : public EventListener { + public: + // Implements step 2. of "inner invoke". + // See: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke + void Invoke(ExecutingContext* context, Event* event, ExceptionState& exception_state) override; + + // Implements "get the current value of the event handler". + // https://html.spec.whatwg.org/C/#getting-the-current-value-of-the-event-handler + // Returns v8::Null with firing error event instead of throwing an exception + // on failing to compile the uncompiled script body in eventHandler's value. + // Also, this can return empty because of crbug.com/881688 . + virtual JSValue GetListenerObject(EventTarget&) = 0; + + // Returns Functions that handles invoked event or undefined without + // throwing any exception. + virtual JSValue GetEffectiveFunction(EventTarget&) = 0; + + bool IsJSBasedEventListener() const override { return true; } + virtual bool IsJSEventListener() const { return false; } + virtual bool IsJSEventHandler() const { return false; } + + protected: + JSBasedEventListener(); + + private: + // Performs "call a user object's operation", required in "inner-invoke". + // "The event handler processing algorithm" corresponds to this in the case of + // EventHandler. + // This may throw an exception on invoking the listener. + // See step 2-10: + // https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke + virtual void InvokeInternal(EventTarget&, Event&, ExceptionState& exception_state) = 0; +}; + +template <> +struct DowncastTraits { + static bool AllowFrom(const EventListener& event_listener) { return event_listener.IsJSBasedEventListener(); } +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc new file mode 100644 index 0000000000..235749cef7 --- /dev/null +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "js_event_handler.h" +#include "bindings/qjs/converter_impl.h" +#include "core/dom/events/event_target.h" +#include "core/events/error_event.h" +#include "event_type_names.h" + +namespace kraken { + +std::unique_ptr JSEventHandler::CreateOrNull(JSContext* ctx, + JSValue value, + JSEventHandler::HandlerType handler_type) { + if (!JS_IsFunction(ctx, value)) { + return nullptr; + } + + return std::make_unique(QJSFunction::Create(ctx, value), handler_type); +} + +bool JSEventHandler::Matches(const EventListener& other) const { + return this == &other; +} + +// https://html.spec.whatwg.org/C/#the-event-handler-processing-algorithm +void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, ExceptionState& exception_state) { + // Step 1. Let callback be the result of getting the current value of the + // event handler given eventTarget and name. + // Step 2. If callback is null, then return. + JSValue listener_value = GetListenerObject(*event.currentTarget()); + if (JS_IsNull(listener_value)) + return; + + // Step 3. Let special error event handling be true if event is an ErrorEvent + // object, event's type is error, and event's currentTarget implements the + // WindowOrWorkerGlobalScope mixin. Otherwise, let special error event + // handling be false. + const bool special_error_event_handling = IsA(event) && event.type() == event_type_names::kerror && + event.currentTarget()->IsWindowOrWorkerGlobalScope(); + + // Step 4. Process the Event object event as follows: + // If special error event handling is true + // Invoke callback with five arguments, the first one having the value of + // event's message attribute, the second having the value of event's + // filename attribute, the third having the value of event's lineno + // attribute, the fourth having the value of event's colno attribute, the + // fifth having the value of event's error attribute, and with the + // callback this value set to event's currentTarget. Let return value be + // the callback's return value. + // Otherwise + // Invoke callback with one argument, the value of which is the Event + // object event, with the callback this value set to event's + // currentTarget. Let return value be the callback's return value. + // If an exception gets thrown by the callback, end these steps and allow + // the exception to propagate. (It will propagate to the DOM event dispatch + // logic, which will then report the exception.) + std::vector arguments; + JSContext* ctx = event_target.ctx(); + + if (special_error_event_handling) { + // TODO: Implement error event handling. + auto* error_event = To(&event); + // The error argument should be initialized to null for dedicated workers. + // https://html.spec.whatwg.org/C/#runtime-script-errors-2 + ScriptValue error_attribute = error_event->error(); + if (error_attribute.IsEmpty()) { + error_attribute = ScriptValue::Empty(event.ctx()); + } + arguments = {ScriptValue(ctx, Converter::ToValue(ctx, error_event->message())), + ScriptValue(ctx, Converter::ToValue(ctx, error_event->filename())), + ScriptValue(ctx, Converter::ToValue(ctx, error_event->lineno())), + ScriptValue(ctx, Converter::ToValue(ctx, error_event->colno())), error_attribute}; + } else { + arguments.emplace_back(ctx, event.ToQuickJS()); + } + + ScriptValue result = event_handler_->Invoke(event.ctx(), ScriptValue(event_target.ctx(), event_target.ToQuickJS()), + arguments.size(), arguments.data()); + if (result.IsException()) { + exception_state.ThrowException(event.ctx(), result.QJSValue()); + return; + } + + // // There is nothing to do if |v8_return_value| is null or undefined. + // // See Step 5. for more information. + if (result.IsEmpty()) { + return; + } + + // https://webidl.spec.whatwg.org/#invoke-a-callback-function + // step 13: Set completion to the result of converting callResult.[[Value]] to + // an IDL value of the same type as the operation's return type. + // + // OnBeforeUnloadEventHandler returns DOMString? while OnErrorEventHandler and + // EventHandler return any, so converting |v8_return_value| to return type is + // necessary only for OnBeforeUnloadEventHandler. + // TODO: special handling for beforeunload event and onerror event. +} + +void JSEventHandler::Trace(GCVisitor* visitor) const {} + +} // namespace kraken diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h new file mode 100644 index 0000000000..2783bb3971 --- /dev/null +++ b/bridge/bindings/qjs/js_event_handler.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ + +#include "foundation/casting.h" +#include "js_based_event_listener.h" + +namespace kraken { + +// |JSEventHandler| implements EventHandler in the HTML standard. +// https://html.spec.whatwg.org/C/#event-handler-attributes +class JSEventHandler : public JSBasedEventListener { + public: + using ImplType = std::shared_ptr; + + enum class HandlerType { + kEventHandler, + // For kOnErrorEventHandler + // https://html.spec.whatwg.org/C/#onerroreventhandler + kOnErrorEventHandler, + // For OnBeforeUnloadEventHandler + // https://html.spec.whatwg.org/C/#onbeforeunloadeventhandler + kOnBeforeUnloadEventHandler, + }; + + static std::unique_ptr CreateOrNull(JSContext* ctx, JSValue value, HandlerType handler_type); + static JSValue ToQuickJS(JSContext* ctx, EventTarget* event_target, EventListener* listener) { + if (auto* event_handler = DynamicTo(listener)) { + return event_handler->GetEffectiveFunction(*event_target); + } + return JS_NULL; + } + + explicit JSEventHandler(const std::shared_ptr& event_handler, HandlerType type) + : type_(type), event_handler_(event_handler){}; + + JSValue GetListenerObject(EventTarget&) override { return event_handler_->ToQuickJS(); } + + JSValue GetEffectiveFunction(EventTarget&) override { return event_handler_->ToQuickJS(); } + + // Helper functions for DowncastTraits. + bool IsJSEventHandler() const override { return true; } + + // For checking special types of EventHandler. + bool IsOnErrorEventHandler() const { return type_ == HandlerType::kOnErrorEventHandler; } + + bool IsOnBeforeUnloadEventHandler() const { return type_ == HandlerType::kOnBeforeUnloadEventHandler; } + + // EventListener overrides: + bool Matches(const EventListener&) const override; + + void Trace(GCVisitor* visitor) const override; + + private: + // JSBasedEventListener override: + // Performs "The event handler processing algorithm" + // https://html.spec.whatwg.org/C/#the-event-handler-processing-algorithm + void InvokeInternal(EventTarget&, Event&, ExceptionState& exception_state) override; + + std::shared_ptr event_handler_; + const HandlerType type_; +}; + +template <> +struct DowncastTraits { + static bool AllowFrom(const EventListener& event_listener) { + auto* js_based = DynamicTo(event_listener); + return js_based && js_based->IsJSEventHandler(); + } +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc new file mode 100644 index 0000000000..0db61c2e43 --- /dev/null +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "js_event_listener.h" +#include "core/dom/events/event_target.h" + +namespace kraken { + +JSEventListener::JSEventListener(std::shared_ptr listener) : event_listener_(listener) {} +JSValue JSEventListener::GetListenerObject(EventTarget&) { + return event_listener_->ToQuickJS(); +} +JSValue JSEventListener::GetEffectiveFunction(EventTarget&) { + return event_listener_->ToQuickJS(); +} +void JSEventListener::InvokeInternal(EventTarget& event_target, Event& event, ExceptionState& exception_state) { + ScriptValue arguments[] = {event.ToValue()}; + + ScriptValue result = event_listener_->Invoke(event.ctx(), event_target.ToValue(), 1, arguments); + if (result.IsException()) { + exception_state.ThrowException(event.ctx(), result.QJSValue()); + return; + } +} + +void JSEventListener::Trace(GCVisitor* visitor) const { + event_listener_->Trace(visitor); +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/js_event_listener.h b/bridge/bindings/qjs/js_event_listener.h new file mode 100644 index 0000000000..c04c4125db --- /dev/null +++ b/bridge/bindings/qjs/js_event_listener.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ + +#include "foundation/casting.h" +#include "js_based_event_listener.h" + +namespace kraken { + +// |JSEventListener| implements EventListener in the DOM standard. +// https://dom.spec.whatwg.org/#callbackdef-eventlistener +class JSEventListener final : public JSBasedEventListener { + public: + using ImplType = std::shared_ptr; + + // TODO: Support IDL EventListener callbackInterface. + static std::unique_ptr CreateOrNull(std::shared_ptr listener) { + return listener ? std::make_unique(listener) : nullptr; + } + + explicit JSEventListener(std::shared_ptr listener); + + JSValue GetListenerObject(EventTarget&) override; + + JSValue GetEffectiveFunction(EventTarget&) override; + + bool IsJSEventListener() const override { return true; } + + bool Matches(const EventListener& other) const override { + const auto* other_listener = DynamicTo(other); + return other_listener && *event_listener_ == *other_listener->event_listener_; + } + + void Trace(GCVisitor* visitor) const override; + + private: + void InvokeInternal(EventTarget&, Event&, ExceptionState& exception_state) override; + + const std::shared_ptr event_listener_; +}; + +template <> +struct DowncastTraits { + static bool AllowFrom(const EventListener& event_listener) { + auto* js_based = DynamicTo(event_listener); + return js_based && js_based->IsJSEventListener(); + } +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/js_context_macros.h b/bridge/bindings/qjs/macros.h similarity index 56% rename from bridge/bindings/qjs/js_context_macros.h rename to bridge/bindings/qjs/macros.h index 55966bb1e0..c880b0bcaa 100644 --- a/bridge/bindings/qjs/js_context_macros.h +++ b/bridge/bindings/qjs/macros.h @@ -2,16 +2,8 @@ * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ -#ifndef KRAKENBRIDGE_JS_CONTEXT_MACROS_H -#define KRAKENBRIDGE_JS_CONTEXT_MACROS_H - -#define OBJECT_INSTANCE(NAME) \ - static NAME* instance(ExecutionContext* context) { \ - if (context->constructorMap.count(#NAME) == 0) { \ - context->constructorMap[#NAME] = static_cast(new NAME(context)); \ - } \ - return static_cast(context->constructorMap[#NAME]); \ - } +#ifndef KRAKENBRIDGE_BINDING_MACROS_H +#define KRAKENBRIDGE_BINDING_MACROS_H #define QJS_GLOBAL_BINDING_FUNCTION(context, function, name, argc) \ { \ @@ -22,40 +14,45 @@ #define IMPL_PROPERTY_GETTER(Constructor, Property) JSValue Constructor::Property##PropertyDescriptor::getter #define IMPL_PROPERTY_SETTER(Constructor, Property) JSValue Constructor::Property##PropertyDescriptor::setter +#define INSTALL_READONLY_PROPERTY(Host, thisObject, property) \ + installPropertyGetter(context.get(), thisObject, #property, Host::property##PropertyDescriptor::getter) + +#define INSTALL_PROPERTY(Host, thisObject, property) \ + installPropertyGetterSetter(context.get(), thisObject, #property, Host::property##PropertyDescriptor::getter, \ + Host::property##PropertyDescriptor::setter) + +#define INSTALL_FUNCTION(Host, thisObject, property, argc) \ + installFunctionProperty(context.get(), thisObject, #property, Host::m_##property##_, 1); + +#define DEFINE_FUNCTION(NAME) \ + static JSValue m_##NAME##_(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + +#define IMPL_FUNCTION(Host, NAME) JSValue Host::m_##NAME##_ + #define DEFINE_PROTOTYPE_READONLY_PROPERTY(PROPERTY) \ class PROPERTY##PropertyDescriptor { \ public: \ static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ - }; \ - ObjectProperty __##PROPERTY##__ { m_context, m_prototypeObject, #PROPERTY, PROPERTY##PropertyDescriptor::getter } - -#define DEFINE_PROTOTYPE_FUNCTION(PROPERTY, ARGS_COUNT) \ - ObjectFunction __##PROPERTY##__ { m_context, m_prototypeObject, #PROPERTY, PROPERTY, ARGS_COUNT } - -#define DEFINE_FUNCTION(PROPERTY, ARGS_COUNT) \ - ObjectFunction __##PROPERTY##__ { m_context, jsObject, #PROPERTY, PROPERTY, ARGS_COUNT } + }; #define DEFINE_PROTOTYPE_PROPERTY(PROPERTY) \ class PROPERTY##PropertyDescriptor { \ public: \ static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ static JSValue setter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ - }; \ - ObjectProperty __##PROPERTY##__ { m_context, m_prototypeObject, #PROPERTY, PROPERTY##PropertyDescriptor::getter, PROPERTY##PropertyDescriptor::setter } + }; #define DEFINE_READONLY_PROPERTY(PROPERTY) \ class PROPERTY##PropertyDescriptor { \ public: \ static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ - }; \ - ObjectProperty __##PROPERTY##__ { m_context, jsObject, #PROPERTY, PROPERTY##PropertyDescriptor::getter } + }; #define DEFINE_PROPERTY(PROPERTY) \ class PROPERTY##PropertyDescriptor { \ public: \ static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ static JSValue setter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ - }; \ - ObjectProperty __##PROPERTY##__ { m_context, jsObject, #PROPERTY, PROPERTY##PropertyDescriptor::getter, PROPERTY##PropertyDescriptor::setter } + }; -#endif // KRAKENBRIDGE_JS_CONTEXT_MACROS_H +#endif // KRAKENBRIDGE_BINDING_MACROS_H diff --git a/bridge/bindings/qjs/member_installer.cc b/bridge/bindings/qjs/member_installer.cc new file mode 100644 index 0000000000..99ba8a44c5 --- /dev/null +++ b/bridge/bindings/qjs/member_installer.cc @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "member_installer.h" +#include +#include "core/executing_context.h" +#include "qjs_engine_patch.h" + +namespace kraken { + +int combinePropFlags(JSPropFlag a, JSPropFlag b) { + return a | b; +} +int combinePropFlags(JSPropFlag a, JSPropFlag b, JSPropFlag c) { + return a | b | c; +} + +// The read object's method or properties via Proxy, we should redirect this_val from Proxy into target property of +// proxy object. +static JSValue handleCallThisOnProxy(JSContext* ctx, + JSValueConst this_val, + int argc, + JSValueConst* argv, + int data_len, + JSValueConst* data) { + JSValue f = data[0]; + JSValue result; + if (JS_IsProxy(this_val)) { + result = JS_Call(ctx, f, JS_GetProxyTarget(this_val), argc, argv); + } else { + // If this_val is undefined or null, this_val should set to globalThis. + if (JS_IsUndefined(this_val) || JS_IsNull(this_val)) { + this_val = JS_GetGlobalObject(ctx); + result = JS_Call(ctx, f, this_val, argc, argv); + JS_FreeValue(ctx, this_val); + } else { + result = JS_Call(ctx, f, this_val, argc, argv); + } + } + return result; +} + +void MemberInstaller::InstallAttributes(ExecutingContext* context, + JSValue root, + std::initializer_list config) { + JSContext* ctx = context->ctx(); + for (auto& c : config) { + JSAtom key = JS_NewAtom(ctx, c.name); + + if (c.getter != nullptr || c.setter != nullptr) { + JSValue getter = JS_NULL; + JSValue setter = JS_NULL; + + if (c.getter != nullptr) { + JSValue f = JS_NewCFunction(ctx, c.getter, "get", 0); + getter = JS_NewCFunctionData(ctx, handleCallThisOnProxy, 0, 0, 1, &f); + JS_FreeValue(ctx, f); + } + if (c.setter != nullptr) { + JSValue f = JS_NewCFunction(ctx, c.setter, "set", 1); + setter = JS_NewCFunctionData(ctx, handleCallThisOnProxy, 1, 0, 1, &f); + JS_FreeValue(ctx, f); + } + JS_DefinePropertyGetSet(ctx, root, key, getter, setter, c.flag); + } else { + JS_DefinePropertyValue(ctx, root, key, c.value, c.flag); + } + + JS_FreeAtom(ctx, key); + } +} + +void MemberInstaller::InstallFunctions(ExecutingContext* context, + JSValue root, + std::initializer_list config) { + JSContext* ctx = context->ctx(); + for (auto& c : config) { + JSValue function = JS_NewCFunction(ctx, c.function, c.name, c.length); + JS_DefinePropertyValueStr(ctx, root, c.name, function, c.flag); + } +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/member_installer.h b/bridge/bindings/qjs/member_installer.h new file mode 100644 index 0000000000..d17b2231c4 --- /dev/null +++ b/bridge/bindings/qjs/member_installer.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MEMBER_INSTALLER_H +#define KRAKENBRIDGE_MEMBER_INSTALLER_H + +#include +#include + +namespace kraken { + +class ExecutingContext; + +// Flags for object properties. +enum JSPropFlag { + normal = JS_PROP_NORMAL, + writable = JS_PROP_WRITABLE, + enumerable = JS_PROP_ENUMERABLE, + configurable = JS_PROP_CONFIGURABLE +}; + +// Combine multiple prop flags. +int combinePropFlags(JSPropFlag a, JSPropFlag b); +int combinePropFlags(JSPropFlag a, JSPropFlag b, JSPropFlag c); + +// A set of utility functions to define attributes members as ES properties. +class MemberInstaller { + public: + struct AttributeConfig { + AttributeConfig& operator=(const AttributeConfig&) = delete; + const char* name; + JSCFunction* getter{nullptr}; + JSCFunction* setter{nullptr}; + JSValue value{JS_NULL}; + int flag{JS_PROP_C_W_E}; // Flags for object properties. + }; + + struct FunctionConfig { + FunctionConfig& operator=(const FunctionConfig&) = delete; + const char* name; + JSCFunction* function; + size_t length; + int flag{JS_PROP_C_W_E}; // Flags for object properties. + }; + + static void InstallAttributes(ExecutingContext* context, JSValue root, std::initializer_list config); + static void InstallFunctions(ExecutingContext* context, JSValue root, std::initializer_list config); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_MEMBER_INSTALLER_H diff --git a/bridge/bindings/qjs/module_manager.cc b/bridge/bindings/qjs/module_manager.cc deleted file mode 100644 index 456672be46..0000000000 --- a/bridge/bindings/qjs/module_manager.cc +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "module_manager.h" -#include "page.h" -#include "qjs_patch.h" - -namespace kraken::binding::qjs { - -JSValue krakenModuleListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': 1 parameter required, but only 0 present."); - } - - JSValue callbackValue = argv[0]; - if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': parameter 1 (callback) must be a function."); - } - - if (!JS_IsFunction(ctx, callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': parameter 1 (callback) must be a function."); - } - - auto context = static_cast(JS_GetContextOpaque(ctx)); - auto* link = new ModuleContext{JS_DupValue(ctx, callbackValue), context}; - list_add_tail(&link->link, &context->module_job_list); - - return JS_NULL; -} - -void handleInvokeModuleTransientCallback(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json) { - auto* moduleContext = static_cast(callbackContext); - ExecutionContext* context = moduleContext->context; - - if (!checkPage(contextId, context)) - return; - if (!context->isValid()) - return; - - if (JS_IsNull(moduleContext->callback)) { - JSValue exception = JS_ThrowTypeError(moduleContext->context->ctx(), "Failed to execute '__kraken_invoke_module__': callback is null."); - context->handleException(&exception); - return; - } - - JSContext* ctx = moduleContext->context->ctx(); - if (!JS_IsObject(moduleContext->callback)) { - return; - } - - JSValue callback = moduleContext->callback; - JSValue returnValue; - if (errmsg != nullptr) { - JS_ThrowInternalError(ctx, "%s", errmsg); - JSValue errorObject = JS_GetException(ctx); - JSValue arguments[] = {errorObject}; - returnValue = JS_Call(ctx, callback, context->global(), 1, arguments); - JS_FreeValue(ctx, errorObject); - } else { - std::u16string argumentString = std::u16string(reinterpret_cast(json->string), json->length); - std::string utf8Arguments = toUTF8(argumentString); - JSValue jsonValue = JS_ParseJSON(ctx, utf8Arguments.c_str(), utf8Arguments.length(), ""); - JSValue arguments[] = {JS_NULL, jsonValue}; - returnValue = JS_Call(ctx, callback, context->global(), 2, arguments); - JS_FreeValue(ctx, jsonValue); - } - - context->drainPendingPromiseJobs(); - - context->handleException(&returnValue); - JS_FreeValue(ctx, moduleContext->callback); - JS_FreeValue(ctx, returnValue); - list_del(&moduleContext->link); -} - -void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json) { - static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); -} - -JSValue krakenInvokeModule(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc < 2) { - return JS_ThrowTypeError(ctx, "Failed to execute 'kraken.invokeModule()': 2 arguments required."); - } - - JSValue moduleNameValue = argv[0]; - JSValue methodValue = argv[1]; - JSValue paramsValue = JS_NULL; - JSValue callbackValue = JS_NULL; - - auto* context = static_cast(JS_GetContextOpaque(ctx)); - - if (argc > 2 && !JS_IsNull(argv[2])) { - paramsValue = argv[2]; - } - - if (argc > 3 && JS_IsObject(argv[3])) { - callbackValue = argv[3]; - } - - std::unique_ptr moduleName = jsValueToNativeString(ctx, moduleNameValue); - std::unique_ptr method = jsValueToNativeString(ctx, methodValue); - std::unique_ptr params; - if (!JS_IsNull(paramsValue)) { - JSValue stringifyedValue = JS_JSONStringify(ctx, paramsValue, JS_NULL, JS_NULL); - // JS_JSONStringify may return JS_EXCEPTION if object is not valid. Return JS_EXCEPTION and let quickjs to handle it. - if (JS_IsException(stringifyedValue)) - return stringifyedValue; - params = jsValueToNativeString(ctx, stringifyedValue); - JS_FreeValue(ctx, stringifyedValue); - } - - if (getDartMethod()->invokeModule == nullptr) { -#if FLUTTER_BACKEND - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); -#else - return JS_NULL; -#endif - } - - ModuleContext* moduleContext; - if (JS_IsNull(callbackValue)) { - auto emptyFunction = [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { return JS_NULL; }; - JSValue callbackFunc = JS_NewCFunction(ctx, emptyFunction, "_f", 0); - moduleContext = new ModuleContext{callbackFunc, context}; - } else { - moduleContext = new ModuleContext{JS_DupValue(ctx, callbackValue), context}; - } - list_add_tail(&moduleContext->link, &context->module_callback_job_list); - - NativeString* result; - - if (!JS_IsNull(callbackValue)) { - result = getDartMethod()->invokeModule(moduleContext, context->getContextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleTransientCallback); - } else { - result = getDartMethod()->invokeModule(moduleContext, context->getContextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleUnexpectedCallback); - } - - moduleName->free(); - method->free(); - if (params != nullptr) { - params->free(); - } - - if (result == nullptr) { - return JS_NULL; - } - - JSValue resultString = JS_NewUnicodeString(context->runtime(), ctx, result->string, result->length); - result->free(); - - return resultString; -} - -JSValue flushUICommand(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (getDartMethod()->flushUICommand == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); - } - getDartMethod()->flushUICommand(); - return JS_NULL; -} - -void bindModuleManager(ExecutionContext* context) { - QJS_GLOBAL_BINDING_FUNCTION(context, krakenModuleListener, "__kraken_module_listener__", 1); - QJS_GLOBAL_BINDING_FUNCTION(context, krakenInvokeModule, "__kraken_invoke_module__", 3); - QJS_GLOBAL_BINDING_FUNCTION(context, flushUICommand, "__kraken_flush_ui_command__", 0); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/module_manager.h b/bridge/bindings/qjs/module_manager.h deleted file mode 100644 index b263152059..0000000000 --- a/bridge/bindings/qjs/module_manager.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_MODULE_MANAGER_H -#define KRAKENBRIDGE_MODULE_MANAGER_H - -#include "executing_context.h" - -namespace kraken::binding::qjs { - -struct ModuleContext { - JSValue callback; - ExecutionContext* context; - list_head link; -}; - -void bindModuleManager(ExecutionContext* context); -void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t contextId, NativeString* errmsg, NativeString* json); -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_MODULE_MANAGER_H diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc new file mode 100644 index 0000000000..c8326788df --- /dev/null +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "native_string_utils.h" +#include "bindings/qjs/qjs_engine_patch.h" + +namespace kraken { + +std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue value) { + bool isValueString = true; + if (JS_IsNull(value)) { + value = JS_NewString(ctx, ""); + isValueString = false; + } else if (!JS_IsString(value)) { + value = JS_ToString(ctx, value); + isValueString = false; + } + + uint32_t length; + uint16_t* buffer = JS_ToUnicode(ctx, value, &length); + std::unique_ptr ptr = std::make_unique(buffer, length); + + if (!isValueString) { + JS_FreeValue(ctx, value); + } + return ptr; +} + +std::unique_ptr stringToNativeString(const std::string& string) { + std::u16string utf16; + fromUTF8(string, utf16); + NativeString tmp{reinterpret_cast(utf16.c_str()), static_cast(utf16.size())}; + return std::make_unique(tmp.string(), tmp.length()); +} + +std::string nativeStringToStdString(const NativeString* native_string) { + std::u16string u16EventType = + std::u16string(reinterpret_cast(native_string->string()), native_string->length()); + return toUTF8(u16EventType); +} + +std::unique_ptr atomToNativeString(JSContext* ctx, JSAtom atom) { + JSValue stringValue = JS_AtomToString(ctx, atom); + std::unique_ptr string = jsValueToNativeString(ctx, stringValue); + JS_FreeValue(ctx, stringValue); + return string; +} + +std::string jsValueToStdString(JSContext* ctx, JSValue& value) { + const char* cString = JS_ToCString(ctx, value); + std::string str = std::string(cString); + JS_FreeCString(ctx, cString); + return str; +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/native_string_utils.h b/bridge/bindings/qjs/native_string_utils.h new file mode 100644 index 0000000000..0731ec811d --- /dev/null +++ b/bridge/bindings/qjs/native_string_utils.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_NATIVE_STRING_UTILS_H +#define KRAKENBRIDGE_NATIVE_STRING_UTILS_H + +#include +#include +#include +#include +#include + +#include "foundation/native_string.h" + +namespace kraken { + +// Convert to string and return a full copy of NativeString from JSValue. +std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue value); + +// Encode utf-8 to utf-16, and return a full copy of NativeString. +std::unique_ptr stringToNativeString(const std::string& string); + +std::string nativeStringToStdString(const NativeString* native_string); + +template +std::string toUTF8(const std::basic_string, std::allocator>& source) { + std::string result; + + std::wstring_convert, T> convertor; + result = convertor.to_bytes(source); + + return result; +} + +template +void fromUTF8(const std::string& source, std::basic_string, std::allocator>& result) { + std::wstring_convert, T> convertor; + result = convertor.from_bytes(source); +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_NATIVE_STRING_UTILS_H diff --git a/bridge/bindings/qjs/native_value.cc b/bridge/bindings/qjs/native_value.cc deleted file mode 100644 index 5fead8ede4..0000000000 --- a/bridge/bindings/qjs/native_value.cc +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "native_value.h" -#include "bindings/qjs/dom/elements/image_element.h" -#include "bindings/qjs/qjs_patch.h" -#include "dom/element.h" -#include "dom/elements/.gen/canvas_element.h" -#include "kraken_bridge.h" - -namespace kraken::binding::qjs { - -#define AnonymousFunctionCallPreFix "_anonymous_fn_" -#define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" - -NativeValue Native_NewNull() { - return (NativeValue){0, .u = {.int64 = 0}, NativeTag::TAG_NULL}; -} - -NativeValue Native_NewString(NativeString* string) { - return (NativeValue){ - 0, - .u = {.ptr = static_cast(string)}, - NativeTag::TAG_STRING, - }; -} - -NativeValue Native_NewCString(std::string string) { - std::unique_ptr nativeString = stringToNativeString(string); - // NativeString owned by NativeValue will be freed by users. - return Native_NewString(nativeString.release()); -} - -NativeValue Native_NewFloat64(double value) { - return (NativeValue){ - value, - .u = {.ptr = nullptr}, - NativeTag::TAG_FLOAT64, - }; -} - -NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr) { - return (NativeValue){static_cast(pointerType), .u = {.ptr = ptr}, NativeTag::TAG_POINTER}; -} - -NativeValue Native_NewBool(bool value) { - return (NativeValue){ - 0, - .u = {.int64 = value ? 1 : 0}, - NativeTag::TAG_BOOL, - }; -} - -NativeValue Native_NewInt32(int32_t value) { - return (NativeValue){ - 0, - .u = {.int64 = value}, - NativeTag::TAG_INT, - }; -} - -NativeValue Native_NewJSON(ExecutionContext* context, JSValue& value) { - JSValue stringifiedValue = JS_JSONStringify(context->ctx(), value, JS_UNDEFINED, JS_UNDEFINED); - if (JS_IsException(stringifiedValue)) - return Native_NewNull(); - - // NativeString owned by NativeValue will be freed by users. - NativeString* string = jsValueToNativeString(context->ctx(), stringifiedValue).release(); - NativeValue result = (NativeValue){ - 0, - .u = {.ptr = static_cast(string)}, - NativeTag::TAG_JSON, - }; - JS_FreeValue(context->ctx(), stringifiedValue); - return result; -} - -void call_native_function(NativeFunctionContext* functionContext, int32_t argc, NativeValue* argv, NativeValue* returnValue) { - auto* context = functionContext->m_context; - auto* arguments = new JSValue[argc]; - for (int i = 0; i < argc; i++) { - arguments[i] = nativeValueToJSValue(context, argv[i]); - } - JSValue result = JS_Call(context->ctx(), functionContext->m_callback, context->global(), argc, arguments); - context->drainPendingPromiseJobs(); - if (context->handleException(&result)) { - *returnValue = jsValueToNativeValue(context->ctx(), result); - } - - JS_FreeValue(context->ctx(), result); - - for (int i = 0; i < argc; i++) { - JS_FreeValue(context->ctx(), arguments[i]); - } - delete[] arguments; - delete functionContext; -} - -NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value) { - if (JS_IsNull(value) || JS_IsUndefined(value)) { - return Native_NewNull(); - } else if (JS_IsBool(value)) { - return Native_NewBool(JS_ToBool(ctx, value)); - } else if (JS_IsNumber(value)) { - uint32_t tag = JS_VALUE_GET_TAG(value); - if (JS_TAG_IS_FLOAT64(tag)) { - double v; - JS_ToFloat64(ctx, &v, value); - return Native_NewFloat64(v); - } else { - int32_t v; - JS_ToInt32(ctx, &v, value); - return Native_NewInt32(v); - } - } else if (JS_IsString(value)) { - // NativeString owned by NativeValue will be freed by users. - NativeString* string = jsValueToNativeString(ctx, value).release(); - return Native_NewString(string); - } else if (JS_IsFunction(ctx, value)) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - auto* functionContext = new NativeFunctionContext{context, value}; - return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); - } else if (JS_IsObject(value)) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { - auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); - return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); - } - - return Native_NewJSON(context, value); - } - - return Native_NewNull(); -} - -NativeFunctionContext::NativeFunctionContext(ExecutionContext* context, JSValue callback) : m_context(context), m_ctx(context->ctx()), m_callback(callback), call(call_native_function) { - JS_DupValue(context->ctx(), callback); - list_add_tail(&link, &m_context->native_function_job_list); -}; - -NativeFunctionContext::~NativeFunctionContext() { - list_del(&link); - JS_FreeValue(m_ctx, m_callback); -} - -static JSValue anonymousFunction(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) { - auto id = magic; - auto* eventTarget = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - - std::string call_params = AnonymousFunctionCallPreFix + std::to_string(id); - - auto* arguments = new NativeValue[argc]; - for (int i = 0; i < argc; i++) { - arguments[i] = jsValueToNativeValue(ctx, argv[i]); - } - - JSValue returnValue = eventTarget->invokeBindingMethod(call_params.c_str(), argc, arguments); - delete[] arguments; - return returnValue; -} - -void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int32_t contextId, const char* errmsg) { - auto* promiseContext = static_cast(callbackContext); - if (!promiseContext->context->isValid()) - return; - if (promiseContext->context->getContextId() != contextId) - return; - - auto* context = promiseContext->context; - - if (nativeValue != nullptr) { - JSValue value = nativeValueToJSValue(promiseContext->context, *nativeValue); - JSValue returnValue = JS_Call(context->ctx(), promiseContext->resolveFunc, context->global(), 1, &value); - context->drainPendingPromiseJobs(); - context->handleException(&returnValue); - JS_FreeValue(context->ctx(), value); - JS_FreeValue(context->ctx(), returnValue); - } else if (errmsg != nullptr) { - JSValue error = JS_NewError(context->ctx()); - JS_DefinePropertyValueStr(context->ctx(), error, "message", JS_NewString(context->ctx(), errmsg), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - JSValue returnValue = JS_Call(context->ctx(), promiseContext->rejectFunc, context->global(), 1, &error); - context->drainPendingPromiseJobs(); - context->handleException(&returnValue); - JS_FreeValue(context->ctx(), error); - JS_FreeValue(context->ctx(), returnValue); - } - - JS_FreeValue(context->ctx(), promiseContext->resolveFunc); - JS_FreeValue(context->ctx(), promiseContext->rejectFunc); - list_del(&promiseContext->link); -} - -static JSValue anonymousAsyncFunction(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) { - JSValue resolving_funcs[2]; - JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); - - auto id = magic; - auto* eventTarget = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - auto* context = eventTarget->context(); - - auto* promiseContext = new PromiseContext{eventTarget, context, resolving_funcs[0], resolving_funcs[1], promise}; - list_add_tail(&promiseContext->link, &context->promise_job_list); - - std::string call_params = AsyncAnonymousFunctionCallPreFix + std::to_string(id); - - auto* arguments = new NativeValue[argc + 3]; - - arguments[0] = Native_NewInt32(context->getContextId()); - arguments[1] = Native_NewPtr(JSPointerType::AsyncContextContext, promiseContext); - arguments[2] = Native_NewPtr(JSPointerType::AsyncContextContext, reinterpret_cast(anonymousAsyncCallback)); - for (int i = 0; i < argc; i++) { - arguments[i + 3] = jsValueToNativeValue(ctx, argv[i]); - } - - eventTarget->invokeBindingMethod(call_params.c_str(), argc + 3, arguments); - delete[] arguments; - - return promise; -} - -JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { - switch (value.tag) { - case NativeTag::TAG_STRING: { - auto* string = static_cast(value.u.ptr); - if (string == nullptr) - return JS_NULL; - JSValue returnedValue = JS_NewUnicodeString(context->runtime(), context->ctx(), string->string, string->length); - string->free(); - return returnedValue; - } - case NativeTag::TAG_INT: { - return JS_NewUint32(context->ctx(), value.u.int64); - } - case NativeTag::TAG_BOOL: { - return JS_NewBool(context->ctx(), value.u.int64 == 1); - } - case NativeTag::TAG_FLOAT64: { - return JS_NewFloat64(context->ctx(), value.float64); - } - case NativeTag::TAG_NULL: { - return JS_NULL; - } - case NativeTag::TAG_JSON: { - auto* str = static_cast(value.u.ptr); - JSValue returnedValue = JS_ParseJSON(context->ctx(), str, strlen(str), ""); - delete str; - return returnedValue; - } - case NativeTag::TAG_POINTER: { - auto* ptr = value.u.ptr; - int ptrType = (int)value.float64; - if (ptrType == static_cast(JSPointerType::NativeBoundingClientRect)) { - return (new BoundingClientRect(context, static_cast(ptr)))->jsObject; - } else if (ptrType == static_cast(JSPointerType::NativeCanvasRenderingContext2D)) { - return (new CanvasRenderingContext2D(context, static_cast(ptr)))->jsObject; - } else if (ptrType == static_cast(JSPointerType::NativeEventTarget)) { - auto* nativeEventTarget = static_cast(ptr); - return JS_DupValue(context->ctx(), nativeEventTarget->instance->jsObject); - } - } - case NativeTag::TAG_FUNCTION: { - int64_t functionId = value.u.int64; - return JS_NewCFunctionData(context->ctx(), anonymousFunction, 4, functionId, 0, nullptr); - } - case NativeTag::TAG_ASYNC_FUNCTION: { - int64_t functionId = value.u.int64; - return JS_NewCFunctionData(context->ctx(), anonymousAsyncFunction, 4, functionId, 0, nullptr); - } - } - return JS_NULL; -} - -std::string nativeStringToStdString(NativeString* nativeString) { - std::u16string u16EventType = std::u16string(reinterpret_cast(nativeString->string), nativeString->length); - return toUTF8(u16EventType); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/pending_promises.cc b/bridge/bindings/qjs/pending_promises.cc new file mode 100644 index 0000000000..eb708d88bc --- /dev/null +++ b/bridge/bindings/qjs/pending_promises.cc @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "pending_promises.h" +#include "script_promise.h" + +namespace kraken { + +void PendingPromises::TrackPendingPromises(ScriptPromise&& promise) { + promises_.emplace_back(promise); +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/pending_promises.h b/bridge/bindings/qjs/pending_promises.h new file mode 100644 index 0000000000..cfe83b8e1b --- /dev/null +++ b/bridge/bindings/qjs/pending_promises.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ + +#include +#include +#include "script_promise.h" + +namespace kraken { + +class PendingPromises { + public: + PendingPromises() = default; + void TrackPendingPromises(ScriptPromise&& promise); + + private: + std::vector promises_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ diff --git a/bridge/bindings/qjs/qjs_patch.cc b/bridge/bindings/qjs/qjs_engine_patch.cc similarity index 85% rename from bridge/bindings/qjs/qjs_patch.cc rename to bridge/bindings/qjs/qjs_engine_patch.cc index 7aac617f65..8a51639ce8 100644 --- a/bridge/bindings/qjs/qjs_patch.cc +++ b/bridge/bindings/qjs/qjs_engine_patch.cc @@ -2,17 +2,11 @@ * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ -#include "qjs_patch.h" +#include "qjs_engine_patch.h" #include #include #include -typedef enum { - JS_GC_PHASE_NONE, - JS_GC_PHASE_DECREF, - JS_GC_PHASE_REMOVE_CYCLES, -} JSGCPhaseEnum; - typedef struct JSProxyData { JSValue target; JSValue handler; @@ -159,9 +153,10 @@ struct JSObject { uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ uint8_t extensible : 1; - uint8_t free_mark : 1; /* only used when freeing objects with cycles */ - uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ - uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed arrays) */ + uint8_t free_mark : 1; /* only used when freeing objects with cycles */ + uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ + uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed + arrays) */ uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */ uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ @@ -186,15 +181,17 @@ struct JSObject { struct JSFloatEnv* float_env; /* JS_CLASS_FLOAT_ENV */ struct JSOperatorSetData* operator_set; /* JS_CLASS_OPERATOR_SET */ #endif - struct JSMapState* map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ - struct JSMapIteratorData* map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ - struct JSArrayIteratorData* array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ - struct JSRegExpStringIteratorData* regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ - struct JSGeneratorData* generator_data; /* JS_CLASS_GENERATOR */ - struct JSProxyData* proxy_data; /* JS_CLASS_PROXY */ - struct JSPromiseData* promise_data; /* JS_CLASS_PROMISE */ - struct JSPromiseFunctionData* promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ - struct JSAsyncFunctionData* async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ + struct JSMapState* map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ + struct JSMapIteratorData* map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ + struct JSArrayIteratorData* array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ + struct JSRegExpStringIteratorData* regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ + struct JSGeneratorData* generator_data; /* JS_CLASS_GENERATOR */ + struct JSProxyData* proxy_data; /* JS_CLASS_PROXY */ + struct JSPromiseData* promise_data; /* JS_CLASS_PROMISE */ + struct JSPromiseFunctionData* + promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ + struct JSAsyncFunctionData* + async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ struct JSAsyncFromSyncIteratorData* async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ struct JSAsyncGeneratorData* async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ @@ -295,9 +292,9 @@ static JSString* js_alloc_string(JSRuntime* runtime, JSContext* ctx, int max_len return p; } -JSValue JS_NewUnicodeString(JSRuntime* runtime, JSContext* ctx, const uint16_t* code, uint32_t length) { +JSValue JS_NewUnicodeString(JSContext* ctx, const uint16_t* code, uint32_t length) { JSString* str; - str = js_alloc_string(runtime, ctx, length, 1); + str = js_alloc_string(JS_GetRuntime(ctx), ctx, length, 1); if (!str) return JS_EXCEPTION; memcpy(str->u.str16, code, length * 2); @@ -319,6 +316,27 @@ bool JS_IsProxy(JSValue value) { return p->class_id == JS_CLASS_PROXY; } +bool JS_IsPromise(JSValue value) { + if (!JS_IsObject(value)) + return false; + JSObject* p = JS_VALUE_GET_OBJ(value); + return p->class_id == JS_CLASS_PROMISE; +} + +bool JS_IsArrayBuffer(JSValue value) { + if (!JS_IsObject(value)) + return false; + JSObject* p = JS_VALUE_GET_OBJ(value); + return p->class_id == JS_CLASS_ARRAY_BUFFER; +} + +bool JS_IsArrayBufferView(JSValue value) { + if (!JS_IsObject(value)) + return false; + JSObject* p = JS_VALUE_GET_OBJ(value); + return p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_DATAVIEW; +} + bool JS_HasClassId(JSRuntime* runtime, JSClassID classId) { if (runtime->class_count <= classId) return false; @@ -329,3 +347,7 @@ JSValue JS_GetProxyTarget(JSValue value) { JSObject* p = JS_VALUE_GET_OBJ(value); return p->u.proxy_data->target; } + +JSGCPhaseEnum JS_GetEnginePhase(JSRuntime* runtime) { + return runtime->gc_phase; +} diff --git a/bridge/bindings/qjs/qjs_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h similarity index 82% rename from bridge/bindings/qjs/qjs_patch.h rename to bridge/bindings/qjs/qjs_engine_patch.h index 929b5093c8..71c5afc5e8 100644 --- a/bridge/bindings/qjs/qjs_patch.h +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -2,8 +2,8 @@ * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ -#ifndef KRAKENBRIDGE_QJS_PATCH_H -#define KRAKENBRIDGE_QJS_PATCH_H +#ifndef KRAKENBRIDGE_QJS_ENGINE_PATCH_H +#define KRAKENBRIDGE_QJS_ENGINE_PATCH_H #include #include @@ -27,6 +27,12 @@ struct JSString { } u; }; +typedef enum { + JS_GC_PHASE_NONE, + JS_GC_PHASE_DECREF, + JS_GC_PHASE_REMOVE_CYCLES, +} JSGCPhaseEnum; + enum { /* classid tag */ /* union usage | properties */ JS_CLASS_OBJECT = 1, /* must be first */ @@ -98,15 +104,39 @@ enum { extern "C" { #endif +static inline bool __JS_AtomIsConst(JSAtom v) { +#if defined(DUMP_LEAKS) && DUMP_LEAKS > 1 + return (int32_t)v <= 0; +#else + return (int32_t)v < JS_ATOM_END; +#endif +} + uint16_t* JS_ToUnicode(JSContext* ctx, JSValueConst value, uint32_t* length); -JSValue JS_NewUnicodeString(JSRuntime* runtime, JSContext* ctx, const uint16_t* code, uint32_t length); +JSValue JS_NewUnicodeString(JSContext* ctx, const uint16_t* code, uint32_t length); JSClassID JSValueGetClassId(JSValue); bool JS_IsProxy(JSValue value); +bool JS_IsPromise(JSValue value); +bool JS_IsArrayBuffer(JSValue value); +bool JS_IsArrayBufferView(JSValue value); bool JS_HasClassId(JSRuntime* runtime, JSClassID classId); JSValue JS_GetProxyTarget(JSValue value); +JSGCPhaseEnum JS_GetEnginePhase(JSRuntime* runtime); + +static inline bool JS_AtomIsTaggedInt(JSAtom v) { + return (v & JS_ATOM_TAG_INT) != 0; +} + +static inline JSAtom JS_AtomFromUInt32(uint32_t v) { + return v | JS_ATOM_TAG_INT; +} + +static inline uint32_t JS_AtomToUInt32(JSAtom atom) { + return atom & ~JS_ATOM_TAG_INT; +} #ifdef __cplusplus } #endif -#endif // KRAKENBRIDGE_QJS_PATCH_H +#endif // KRAKENBRIDGE_QJS_ENGINE_PATCH_H diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc new file mode 100644 index 0000000000..934bfdae21 --- /dev/null +++ b/bridge/bindings/qjs/qjs_function.cc @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "qjs_function.h" +#include +#include "cppgc/gc_visitor.h" + +namespace kraken { + +bool QJSFunction::IsFunction(JSContext* ctx) { + return JS_IsFunction(ctx, function_); +} + +ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int32_t argc, ScriptValue* arguments) { + // 'm_function' might be destroyed when calling itself (if it frees the handler), so must take extra care. + JS_DupValue(ctx, function_); + + JSValue argv[std::max(1, argc)]; + + for (int i = 0; i < argc; i++) { + argv[0 + i] = arguments[i].QJSValue(); + } + + JSValue returnValue = JS_Call(ctx, function_, this_val.QJSValue(), argc, argv); + + // Free the previous duplicated function. + JS_FreeValue(ctx, function_); + + return ScriptValue(ctx, returnValue); +} + +void QJSFunction::Trace(GCVisitor* visitor) const { + visitor->Trace(function_); +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h new file mode 100644 index 0000000000..81951f336c --- /dev/null +++ b/bridge/bindings/qjs/qjs_function.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_QJS_FUNCTION_H +#define KRAKENBRIDGE_QJS_FUNCTION_H + +#include "script_value.h" + +namespace kraken { + +// https://webidl.spec.whatwg.org/#dfn-callback-interface +// QJSFunction memory are auto managed by std::shared_ptr. +class QJSFunction { + public: + static std::shared_ptr Create(JSContext* ctx, JSValue function) { + return std::make_shared(ctx, function); + } + explicit QJSFunction(JSContext* ctx, JSValue function) : ctx_(ctx), function_(JS_DupValue(ctx, function)){}; + // This safe to free function_ at GC stage. + ~QJSFunction() { JS_FreeValue(ctx_, function_); } + + bool IsFunction(JSContext* ctx); + + JSValue ToQuickJS() { return JS_DupValue(ctx_, function_); }; + + // Performs "invoke". + // https://webidl.spec.whatwg.org/#invoke-a-callback-function + ScriptValue Invoke(JSContext* ctx, const ScriptValue& this_val, int32_t argc, ScriptValue* arguments); + + bool operator==(const QJSFunction& other) { + return JS_VALUE_GET_PTR(function_) == JS_VALUE_GET_PTR(other.function_); + }; + + void Trace(GCVisitor* visitor) const; + + private: + JSContext* ctx_{nullptr}; + JSValue function_{JS_NULL}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_QJS_FUNCTION_H diff --git a/bridge/bindings/qjs/qjs_interface_bridge.cc b/bridge/bindings/qjs/qjs_interface_bridge.cc new file mode 100644 index 0000000000..8c6e207cef --- /dev/null +++ b/bridge/bindings/qjs/qjs_interface_bridge.cc @@ -0,0 +1,9 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "qjs_interface_bridge.h" +#include "core/executing_context.h" + +namespace kraken {} // namespace kraken diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h new file mode 100644 index 0000000000..8358d6caef --- /dev/null +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ + +#include "core/executing_context.h" +#include "script_wrappable.h" + +namespace kraken { + +template +class QJSInterfaceBridge { + public: + static T* ToWrappable(ExecutingContext* context, JSValue value) { + return HasInstance(context, value) ? toScriptWrappable(value) : nullptr; + } + + static bool HasInstance(ExecutingContext* context, JSValue value) { + return JS_IsInstanceOf(context->ctx(), value, + context->contextData()->constructorForType(QJST::GetWrapperTypeInfo())); + }; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ diff --git a/bridge/bindings/qjs/qjs_patch_test.cc b/bridge/bindings/qjs/qjs_patch_test.cc index 76dd18a29d..3f833358f8 100644 --- a/bridge/bindings/qjs/qjs_patch_test.cc +++ b/bridge/bindings/qjs/qjs_patch_test.cc @@ -2,9 +2,9 @@ * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ -#include "qjs_patch.h" #include #include "gtest/gtest.h" +#include "qjs_engine_patch.h" TEST(JS_ToUnicode, asciiWords) { JSRuntime* runtime = JS_NewRuntime(); @@ -59,7 +59,8 @@ TEST(JS_NewUnicodeString, fromAscii) { JSRuntime* runtime = JS_NewRuntime(); JSContext* ctx = JS_NewContext(runtime); std::u16string source = u"helloworld"; - JSValue result = JS_NewUnicodeString(runtime, ctx, reinterpret_cast(source.c_str()), source.length()); + JSValue result = + JS_NewUnicodeString(runtime, ctx, reinterpret_cast(source.c_str()), source.length()); const char* str = JS_ToCString(ctx, result); EXPECT_STREQ(str, "helloworld"); @@ -73,7 +74,8 @@ TEST(JS_NewUnicodeString, fromChieseCode) { JSRuntime* runtime = JS_NewRuntime(); JSContext* ctx = JS_NewContext(runtime); std::u16string source = u"a你的名字12345"; - JSValue result = JS_NewUnicodeString(runtime, ctx, reinterpret_cast(source.c_str()), source.length()); + JSValue result = + JS_NewUnicodeString(runtime, ctx, reinterpret_cast(source.c_str()), source.length()); uint32_t length; uint16_t* buffer = JS_ToUnicode(ctx, result, &length); std::u16string bufferString = std::u16string(reinterpret_cast(buffer), length); diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index d2a1afd897..efe796274b 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -3,64 +3,66 @@ */ #include "rejected_promises.h" -#include "executing_context.h" +#include "core/executing_context.h" -namespace kraken::binding::qjs { +namespace kraken { -RejectedPromises::Message::Message(ExecutionContext* context, JSValue promise, JSValue reason) - : m_runtime(context->runtime()), m_promise(JS_DupValue(context->ctx(), promise)), m_reason(JS_DupValue(context->ctx(), reason)) {} +RejectedPromises::Message::Message(ExecutingContext* context, JSValue promise, JSValue reason) + : m_runtime(ScriptState::runtime()), + m_promise(JS_DupValue(context->ctx(), promise)), + m_reason(JS_DupValue(context->ctx(), reason)) {} RejectedPromises::Message::~Message() { JS_FreeValueRT(m_runtime, m_promise); JS_FreeValueRT(m_runtime, m_reason); } -void RejectedPromises::trackUnhandledPromiseRejection(ExecutionContext* context, JSValue promise, JSValue reason) { +void RejectedPromises::TrackUnhandledPromiseRejection(ExecutingContext* context, JSValue promise, JSValue reason) { void* ptr = JS_VALUE_GET_PTR(promise); - if (m_unhandledRejections.count(ptr) == 0) { - m_unhandledRejections[ptr] = std::make_unique(context, promise, reason); + if (unhandled_rejections_.count(ptr) == 0) { + unhandled_rejections_[ptr] = std::make_unique(context, promise, reason); } // One promise will never have more than one unhandled rejection. } -void RejectedPromises::trackHandledPromiseRejection(ExecutionContext* context, JSValue promise, JSValue reason) { +void RejectedPromises::TrackHandledPromiseRejection(ExecutingContext* context, JSValue promise, JSValue reason) { void* ptr = JS_VALUE_GET_PTR(promise); // Unhandled promise are handled in a sync script call. It's file so we remove the recording of this promise. - if (m_unhandledRejections.count(ptr) > 0) { - m_unhandledRejections.erase(ptr); + if (unhandled_rejections_.count(ptr) > 0) { + unhandled_rejections_.erase(ptr); } else { // This promise are handled in the next script call, we save this operation to trigger handledRejection event. - m_reportHandledRejection.push_back(std::make_unique(context, promise, reason)); + report_handled_rejection_.push_back(std::make_unique(context, promise, reason)); } } -void RejectedPromises::process(ExecutionContext* context) { +void RejectedPromises::Process(ExecutingContext* context) { // Copy m_unhandledRejections to avoid endless recursion call. std::unordered_map> unhandledRejections; - for (auto& entry : m_unhandledRejections) { - unhandledRejections[entry.first] = std::unique_ptr(m_unhandledRejections[entry.first].release()); + for (auto& entry : unhandled_rejections_) { + unhandledRejections[entry.first] = std::unique_ptr(unhandled_rejections_[entry.first].release()); } - m_unhandledRejections.clear(); + unhandled_rejections_.clear(); // Copy m_reportHandledRejection to avoid endless recursion call. std::vector> reportHandledRejection; reportHandledRejection.reserve(reportHandledRejection.size()); - for (auto& entry : m_reportHandledRejection) { + for (auto& entry : report_handled_rejection_) { reportHandledRejection.push_back(std::unique_ptr(entry.release())); } - m_reportHandledRejection.clear(); + report_handled_rejection_.clear(); // Dispatch unhandled rejectionEvents. for (auto& entry : unhandledRejections) { - context->reportError(entry.second->m_reason); - context->dispatchGlobalUnhandledRejectionEvent(context, entry.second->m_promise, entry.second->m_reason); + context->ReportError(entry.second->m_reason); + context->DispatchGlobalUnhandledRejectionEvent(context, entry.second->m_promise, entry.second->m_reason); } // Dispatch handledRejection events. for (auto& entry : reportHandledRejection) { - context->dispatchGlobalRejectionHandledEvent(context, entry->m_promise, entry->m_reason); + context->DispatchGlobalRejectionHandledEvent(context, entry->m_promise, entry->m_reason); } } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/rejected_promises.h b/bridge/bindings/qjs/rejected_promises.h index dbc558c06d..a79c7b9f51 100644 --- a/bridge/bindings/qjs/rejected_promises.h +++ b/bridge/bindings/qjs/rejected_promises.h @@ -10,15 +10,15 @@ #include #include -namespace kraken::binding::qjs { +namespace kraken { -class ExecutionContext; +class ExecutingContext; class RejectedPromises { public: class Message { public: - Message(ExecutionContext* context, JSValue promise, JSValue reason); + Message(ExecutingContext* context, JSValue promise, JSValue reason); ~Message(); JSRuntime* m_runtime{nullptr}; @@ -27,17 +27,17 @@ class RejectedPromises { }; // Keeping track unhandled promise rejection in current context, and throw unhandledRejection error - void trackUnhandledPromiseRejection(ExecutionContext* context, JSValue promise, JSValue reason); + void TrackUnhandledPromiseRejection(ExecutingContext* context, JSValue promise, JSValue reason); // When unhandled promise are handled in the future, should trigger a handledRejection event. - void trackHandledPromiseRejection(ExecutionContext* context, JSValue promise, JSValue reason); + void TrackHandledPromiseRejection(ExecutingContext* context, JSValue promise, JSValue reason); // Trigger events after promise executed. - void process(ExecutionContext* context); + void Process(ExecutingContext* context); private: - std::unordered_map> m_unhandledRejections; - std::vector> m_reportHandledRejection; + std::unordered_map> unhandled_rejections_; + std::vector> report_handled_rejection_; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_REJECTED_PROMISES_H_ diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc new file mode 100644 index 0000000000..c23eee774a --- /dev/null +++ b/bridge/bindings/qjs/script_promise.cc @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_promise.h" +#include "qjs_engine_patch.h" + +namespace kraken { + +ScriptPromise::ScriptPromise(JSContext* ctx, JSValue promise) : ctx_(ctx) { + if (JS_IsUndefined(promise) || JS_IsNull(promise)) + return; + + if (!JS_IsPromise(promise)) { + return; + } + + promise_ = ScriptValue(ctx, promise); +} + +JSValue ScriptPromise::ToQuickJS() { + return JS_DupValue(ctx_, promise_.QJSValue()); +} + +void ScriptPromise::Trace(GCVisitor* visitor) {} + +} // namespace kraken diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h new file mode 100644 index 0000000000..5a58cb2407 --- /dev/null +++ b/bridge/bindings/qjs/script_promise.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ + +#include +#include "foundation/macros.h" +#include "script_value.h" + +namespace kraken { + +// ScriptPromise is the class for representing Promise values in C++ world. +// ScriptPromise holds a Promise. +// So holding a ScriptPromise as a member variable in DOM object causes +// memory leaks since it has a reference from C++ to QuickJS. +class ScriptPromise final { + KRAKEN_DISALLOW_NEW(); + + public: + ScriptPromise() = default; + ScriptPromise(JSContext* ctx, JSValue promise); + + JSValue ToQuickJS(); + + void Trace(GCVisitor* visitor); + + private: + JSContext* ctx_; + ScriptValue promise_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc new file mode 100644 index 0000000000..a7b2e615aa --- /dev/null +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_promise_resolver.h" +#include "core/executing_context.h" +#include "pending_promises.h" + +namespace kraken { + +std::shared_ptr ScriptPromiseResolver::Create(ExecutingContext* context) { + return std::make_shared(context); +} + +ScriptPromiseResolver::ScriptPromiseResolver(ExecutingContext* context) + : context_(context), state_(ResolutionState::kPending) { + JSValue resolving_funcs[2]; + promise_ = JS_NewPromiseCapability(context->ctx(), resolving_funcs); + resolve_func_ = resolving_funcs[0]; + reject_func_ = resolving_funcs[1]; + + context->GetPendingPromises()->TrackPendingPromises(ScriptPromise(context_->ctx(), promise_)); +} + +ScriptPromiseResolver::~ScriptPromiseResolver() { + JS_FreeValue(context_->ctx(), promise_); + JS_FreeValue(context_->ctx(), resolve_func_); + JS_FreeValue(context_->ctx(), reject_func_); +} + +ScriptPromise ScriptPromiseResolver::Promise() { + return ScriptPromise(context_->ctx(), promise_); +} + +void ScriptPromiseResolver::ResolveOrRejectImmediately(JSValue value) { + { + if (state_ == kResolving) { + JSValue arguments[] = {value}; + JSValue return_value = JS_Call(context_->ctx(), resolve_func_, JS_NULL, 1, arguments); + if (JS_IsException(return_value)) { + context_->HandleException(&return_value); + } + JS_FreeValue(context_->ctx(), return_value); + } else { + assert(state_ == kRejecting); + JSValue arguments[] = {value}; + JSValue return_value = JS_Call(context_->ctx(), reject_func_, JS_NULL, 1, arguments); + if (JS_IsException(return_value)) { + context_->HandleException(&return_value); + } + JS_FreeValue(context_->ctx(), return_value); + } + } + context_->DrainPendingPromiseJobs(); +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h new file mode 100644 index 0000000000..72bee032d8 --- /dev/null +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ + +#include "converter_impl.h" +#include "script_promise.h" +#include "to_quickjs.h" + +namespace kraken { + +class ScriptPromiseResolver { + public: + static std::shared_ptr Create(ExecutingContext* context); + ScriptPromiseResolver() = delete; + ScriptPromiseResolver(ExecutingContext* context); + ~ScriptPromiseResolver(); + + // Return a promise object and wait to be resolve or reject. + // Note that an empty ScriptPromise will be returned after resolve or + // reject is called. + ScriptPromise Promise(); + + // Anything that can be passed to toQuickJS can be passed to this function. + template + void Resolve(T value) { + ResolveOrReject(value, kResolving); + } + + // Anything that can be passed to toQuickJS can be passed to this function. + template + void Reject(T value) { + ResolveOrReject(value, kRejecting); + } + + private: + enum ResolutionState { + kPending, + kResolving, + kRejecting, + kDetached, + }; + + ExecutingContext* GetExecutionContext() const { return context_; } + + template + void ResolveOrReject(T value, ResolutionState new_state) { + if (state_ != kPending || !context_->IsValid() || !context_) + return; + assert(new_state == kResolving || new_state == kRejecting); + state_ = new_state; + JSValue qjs_value = toQuickJS(context_->ctx(), value); + ResolveOrRejectImmediately(qjs_value); + JS_FreeValue(context_->ctx(), qjs_value); + } + + void ResolveOrRejectImmediately(JSValue value); + + ResolutionState state_; + ExecutingContext* context_{nullptr}; + JSValue promise_{JS_NULL}; + JSValue resolve_func_{JS_NULL}; + JSValue reject_func_{JS_NULL}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc new file mode 100644 index 0000000000..170b397006 --- /dev/null +++ b/bridge/bindings/qjs/script_value.cc @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_value.h" +#include +#include "core/executing_context.h" +#include "cppgc/gc_visitor.h" +#include "foundation/native_value_converter.h" +#include "native_string_utils.h" +#include "qjs_bounding_client_rect.h" +#include "qjs_engine_patch.h" + +namespace kraken { + +ScriptValue ScriptValue::CreateErrorObject(JSContext* ctx, const char* errmsg) { + JS_ThrowInternalError(ctx, "%s", errmsg); + JSValue errorObject = JS_GetException(ctx); + ScriptValue result = ScriptValue(ctx, errorObject); + JS_FreeValue(ctx, errorObject); + return result; +} + +ScriptValue ScriptValue::CreateJsonObject(JSContext* ctx, const char* jsonString, size_t length) { + JSValue jsonValue = JS_ParseJSON(ctx, jsonString, length, ""); + ScriptValue result = ScriptValue(ctx, jsonValue); + JS_FreeValue(ctx, jsonValue); + return result; +} + +ScriptValue ScriptValue::Empty(JSContext* ctx) { + return ScriptValue(ctx); +} + +ScriptValue::ScriptValue(const ScriptValue& value) { + if (&value != this) { + value_ = JS_DupValue(ctx_, value.value_); + } + ctx_ = value.ctx_; +} +ScriptValue& ScriptValue::operator=(const ScriptValue& value) { + if (&value != this) { + value_ = JS_DupValue(ctx_, value.value_); + } + ctx_ = value.ctx_; + return *this; +} + +ScriptValue::ScriptValue(ScriptValue&& value) noexcept { + if (&value != this) { + value_ = JS_DupValue(ctx_, value.value_); + } + ctx_ = value.ctx_; +} +ScriptValue& ScriptValue::operator=(ScriptValue&& value) noexcept { + if (&value != this) { + value_ = JS_DupValue(ctx_, value.value_); + } + ctx_ = value.ctx_; + return *this; +} + +JSValue ScriptValue::QJSValue() const { + return value_; +} + +ScriptValue ScriptValue::ToJSONStringify(ExceptionState* exception) const { + JSValue stringifyed = JS_JSONStringify(ctx_, value_, JS_NULL, JS_NULL); + ScriptValue result = ScriptValue(ctx_, stringifyed); + // JS_JSONStringify may return JS_EXCEPTION if object is not valid. Return JS_EXCEPTION and let quickjs to handle it. + if (result.IsException()) { + exception->ThrowException(ctx_, result.value_); + result = ScriptValue::Empty(ctx_); + } + JS_FreeValue(ctx_, stringifyed); + return result; +} + +AtomicString ScriptValue::ToString() const { + return AtomicString(ctx_, value_); +} + +NativeValue ScriptValue::ToNative() const { + if (JS_IsNull(value_) || JS_IsUndefined(value_)) { + return Native_NewNull(); + } else if (JS_IsBool(value_)) { + return Native_NewBool(JS_ToBool(ctx_, value_)); + } else if (JS_IsNumber(value_)) { + uint32_t tag = JS_VALUE_GET_TAG(value_); + if (JS_TAG_IS_FLOAT64(tag)) { + double v; + JS_ToFloat64(ctx_, &v, value_); + return Native_NewFloat64(v); + } else { + int32_t v; + JS_ToInt32(ctx_, &v, value_); + return Native_NewInt64(v); + } + } else if (JS_IsString(value_)) { + // NativeString owned by NativeValue will be freed by users. + NativeString* string = this->ToString().ToNativeString().release(); + return NativeValueConverter::ToNativeValue(string); + } + + // else if (JS_IsFunction(ctx_, value_)) { + // auto* context = static_cast(JS_GetContextOpaque(ctx_)); + // auto* functionContext = new NativeFunctionContext{context, value_}; + // return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); + // } + // + else if (JS_IsObject(value_)) { + // auto* context = static_cast(JS_GetContextOpaque(ctx_)); + // auto* context = static_cast(JS_GetContextOpaque(ctx)); + // if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { + // auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); + // return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); + // } + + // return Native_NewJSON(context, value); + } + + return Native_NewNull(); +} + +bool ScriptValue::IsException() { + return JS_IsException(value_); +} + +bool ScriptValue::IsEmpty() { + return JS_IsNull(value_) || JS_IsUndefined(value_); +} + +bool ScriptValue::IsObject() { + return JS_IsObject(value_); +} + +bool ScriptValue::IsString() { + return JS_IsString(value_); +} + +void ScriptValue::Trace(GCVisitor* visitor) { + visitor->Trace(value_); +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h new file mode 100644 index 0000000000..710d0a994f --- /dev/null +++ b/bridge/bindings/qjs/script_value.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_SCRIPT_VALUE_H +#define KRAKENBRIDGE_SCRIPT_VALUE_H + +#include +#include + +#include "atomic_string.h" +#include "exception_state.h" +#include "foundation/macros.h" +#include "foundation/native_string.h" + +namespace kraken { + +class ExecutingContext; +class WrapperTypeInfo; +class NativeValue; +class GCVisitor; + +// ScriptValue is a stack allocate only QuickJS JSValue wrapper ScriptValuewhich hold all information to hide out +// QuickJS running details. +class ScriptValue final { + // ScriptValue should only allocate at stack. + KRAKEN_DISALLOW_NEW(); + + public: + // Create an errorObject from string error message. + static ScriptValue CreateErrorObject(JSContext* ctx, const char* errmsg); + // Create an object from JSON string. + static ScriptValue CreateJsonObject(JSContext* ctx, const char* jsonString, size_t length); + + // Create an empty ScriptValue; + static ScriptValue Empty(JSContext* ctx); + // Wrap an Quickjs JSValue to ScriptValue. + explicit ScriptValue(JSContext* ctx, JSValue value) : ctx_(ctx), value_(JS_DupValue(ctx, value)){}; + explicit ScriptValue(JSContext* ctx, const NativeString* string) + : ctx_(ctx), value_(JS_NewUnicodeString(ctx, string->string(), string->length())) {} + explicit ScriptValue(JSContext* ctx, double v) : ctx_(ctx), value_(JS_NewFloat64(ctx, v)) {} + explicit ScriptValue(JSContext* ctx) : ctx_(ctx){}; + ScriptValue() = default; + + // Copy and assignment + ScriptValue(ScriptValue const& value); + ScriptValue& operator=(const ScriptValue& value); + + // Move operations + ScriptValue(ScriptValue&& value) noexcept; + ScriptValue& operator=(ScriptValue&& value) noexcept; + + ~ScriptValue() { JS_FreeValue(ctx_, value_); }; + + JSValue QJSValue() const; + // Create a new ScriptValue from call JSON.stringify to current value. + ScriptValue ToJSONStringify(ExceptionState* exception) const; + AtomicString ToString() const; + NativeValue ToNative() const; + + bool IsException(); + bool IsEmpty(); + bool IsObject(); + bool IsString(); + + void Trace(GCVisitor* visitor); + + private: + JSContext* ctx_{nullptr}; + JSValue value_{JS_NULL}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_SCRIPT_VALUE_H diff --git a/bridge/bindings/qjs/script_value_test.cc b/bridge/bindings/qjs/script_value_test.cc new file mode 100644 index 0000000000..505d2efd63 --- /dev/null +++ b/bridge/bindings/qjs/script_value_test.cc @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "script_value.h" +#include +#include +#include "atomic_string.h" +#include "gtest/gtest.h" + +using namespace kraken; + +using TestCallback = void (*)(JSContext* ctx); + +void TestScriptValue(TestCallback callback) { + JSRuntime* runtime = JS_NewRuntime(); + JSContext* ctx = JS_NewContext(runtime); + + callback(ctx); + + JS_FreeContext(ctx); + JS_FreeRuntime(runtime); +} + +TEST(ScriptValue, createErrorObject) { + TestScriptValue([](JSContext* ctx) { + ScriptValue value = ScriptValue::CreateErrorObject(ctx, "error"); + EXPECT_EQ(JS_IsError(ctx, value.QJSValue()), true); + }); +} + +TEST(ScriptValue, CreateJsonObject) { + TestScriptValue([](JSContext* ctx) { + std::string code = "{\"name\": 1}"; + ScriptValue value = ScriptValue::CreateJsonObject(ctx, code.c_str(), code.size()); + EXPECT_EQ(value.IsObject(), true); + }); +} + +TEST(ScriptValue, Empty) { + TestScriptValue([](JSContext* ctx) { + ScriptValue empty = ScriptValue::Empty(ctx); + EXPECT_EQ(empty.IsEmpty(), true); + }); +} + +TEST(ScriptValue, ToString) { + TestScriptValue([](JSContext* ctx) { + std::string code = "{\"name\": 1}"; + ScriptValue json = ScriptValue::CreateJsonObject(ctx, code.c_str(), code.size()); + AtomicString string = json.ToString(); + EXPECT_STREQ(string.ToStdString().c_str(), "[object Object]"); + }); +} + +TEST(ScriptValue, CopyAssignment) { + TestScriptValue([](JSContext* ctx) { + std::string code = "{\"name\":1}"; + ScriptValue json = ScriptValue::CreateJsonObject(ctx, code.c_str(), code.size()); + struct P { + ScriptValue value; + }; + P p; + p.value = json; + EXPECT_STREQ(p.value.ToJSONStringify(nullptr).ToString().ToStdString().c_str(), code.c_str()); + }); +} + +TEST(ScriptValue, MoveAssignment) { + TestScriptValue([](JSContext* ctx) { + ScriptValue other; + { + std::string code = "{\"name\":1}"; + other = ScriptValue::CreateJsonObject(ctx, code.c_str(), code.size()); + } + + EXPECT_STREQ(other.ToJSONStringify(nullptr).ToString().ToStdString().c_str(), "{\"name\":1}"); + }); +} diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc new file mode 100644 index 0000000000..b151b745f6 --- /dev/null +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_wrappable.h" +#include "core/executing_context.h" +#include "cppgc/gc_visitor.h" + +namespace kraken { + +ScriptWrappable::ScriptWrappable(JSContext* ctx) + : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), context_(ExecutingContext::From(ctx)) {} + +JSValue ScriptWrappable::ToQuickJS() const { + return JS_DupValue(ctx_, jsObject_); +} + +JSValue ScriptWrappable::ToQuickJSUnsafe() const { + return jsObject_; +} + +ScriptValue ScriptWrappable::ToValue() { + return ScriptValue(ctx_, jsObject_); +} + +/// This callback will be called when QuickJS GC is running at marking stage. +/// Users of this class should override `void Trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func)` to +/// tell GC which member of their class should be collected by GC. +static void HandleJSObjectGCMark(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) { + auto* object = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); + GCVisitor visitor{rt, mark_func}; + object->Trace(&visitor); +} + +/// This callback will be called when QuickJS GC will release the `jsObject` object memory of this class. +/// The deconstruct method of this class will be called and all memory about this class will be freed when finalize +/// completed. +static void HandleJSObjectFinalized(JSRuntime* rt, JSValue val) { + auto* object = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); + delete object; +} + +/// This callback will be called when JS code access this object using [] or `.` operator. +/// When exec `obj[1]`, it will call indexed_property_getter_handler_ defined in WrapperTypeInfo. +/// When exec `obj['hello']`, it will call string_property_getter_handler_ defined in WrapperTypeInfo. +static JSValue HandleJSPropertyGetterCallback(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { + ExecutingContext* context = ExecutingContext::From(ctx); + auto* object = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* wrapper_type_info = object->GetWrapperTypeInfo(); + + JSValue prototypeObject = context->contextData()->prototypeForType(wrapper_type_info); + if (JS_HasProperty(ctx, prototypeObject, atom)) { + JSValue ret = JS_GetPropertyInternal(ctx, prototypeObject, atom, obj, 0); + return ret; + } + + if (wrapper_type_info->indexed_property_getter_handler_ != nullptr && JS_AtomIsTaggedInt(atom)) { + return wrapper_type_info->indexed_property_getter_handler_(ctx, obj, JS_AtomToUInt32(atom)); + } else if (wrapper_type_info->string_property_getter_handler_ != nullptr) { + return wrapper_type_info->string_property_getter_handler_(ctx, obj, atom); + } + + return JS_UNDEFINED; +} + +/// This callback will be called when JS code set property on this object using [] or `.` operator. +/// When exec `obj[1] = 1`, it will call +static int HandleJSPropertySetterCallback(JSContext* ctx, + JSValueConst obj, + JSAtom atom, + JSValueConst value, + JSValueConst receiver, + int flags) { + auto* object = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* wrapper_type_info = object->GetWrapperTypeInfo(); + + 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) { + return wrapper_type_info->string_property_setter_handler_(ctx, obj, atom, value); + } + + return false; +} + +/// This callback will be called when JS code check property exit on this object using `in` operator. +/// Wehn exec `'prop' in obj`, it will call. +static int HandleJSPropertyCheckerCallback(JSContext* ctx, JSValueConst obj, JSAtom atom) { + auto* object = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* wrapper_type_info = object->GetWrapperTypeInfo(); + + return wrapper_type_info->string_property_checker_handler_(ctx, obj, atom); +} + +static int HandleJSGetOwnPropertyNames(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, JSValueConst obj) { + // All props and methods are finded in prototype object of scriptwrappable. + JSValue proto = JS_GetPrototype(ctx, obj); + bool result = JS_GetOwnPropertyNames(ctx, ptab, plen, proto, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK); + JS_FreeValue(ctx, proto); + return result; +}; + +static int HandleJSGetOwnProperty(JSContext* ctx, JSPropertyDescriptor* desc, JSValueConst obj, JSAtom prop) { + // Call JSGetOwnPropertyNames will also call HandleJSGetOwnProperty for secondary verify. + JSValue proto = JS_GetPrototype(ctx, obj); + bool result = JS_GetOwnProperty(ctx, desc, proto, prop); + JS_FreeValue(ctx, proto); + return result; +} + +void ScriptWrappable::InitializeQuickJSObject() { + auto* wrapper_type_info = GetWrapperTypeInfo(); + JSRuntime* runtime = runtime_; + + /// ClassId should be a static QJSValue to make sure JSClassDef when this class are created at the first class. + if (!JS_HasClassId(runtime, wrapper_type_info->classId)) { + /// Basic template to describe the behavior about this class. + JSClassDef def{}; + + // Define object's className + def.class_name = wrapper_type_info->className; + + // Register the hooks when GC marking at this object. + def.gc_mark = HandleJSObjectGCMark; + + // Define the custom behavior of object. + auto* exotic_methods = new JSClassExoticMethods{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; + + // Define the callback when access object property. + if (UNLIKELY(wrapper_type_info->indexed_property_getter_handler_ != nullptr || + wrapper_type_info->string_property_getter_handler_ != nullptr)) { + exotic_methods->get_property = HandleJSPropertyGetterCallback; + } + + // Define the callback when set object property. + if (UNLIKELY(wrapper_type_info->indexed_property_getter_handler_ != nullptr || + wrapper_type_info->string_property_setter_handler_ != nullptr)) { + exotic_methods->set_property = HandleJSPropertySetterCallback; + } + + // Define the callback when check object property exist. + if (UNLIKELY(wrapper_type_info->string_property_checker_handler_ != nullptr)) { + exotic_methods->has_property = HandleJSPropertyCheckerCallback; + } + + // Support iterate script wrappable defined properties. + exotic_methods->get_own_property_names = HandleJSGetOwnPropertyNames; + exotic_methods->get_own_property = HandleJSGetOwnProperty; + + def.exotic = exotic_methods; + def.finalizer = HandleJSObjectFinalized; + + JS_NewClass(runtime, wrapper_type_info->classId, &def); + } + + /// The JavaScript object underline this class. This `jsObject` is the JavaScript object which can be directly access + /// within JavaScript code. When the reference count of `jsObject` decrease to 0, QuickJS will trigger `finalizer` + /// callback and free `jsObject` memory. When QuickJS GC found `jsObject` at marking stage, `gc_mark` callback will be + /// triggered. + jsObject_ = JS_NewObjectClass(ctx_, wrapper_type_info->classId); + JS_SetOpaque(jsObject_, this); + + // Let our instance into inherit prototype methods. + JSValue prototype = GetExecutingContext()->contextData()->prototypeForType(wrapper_type_info); + JS_SetPrototype(ctx_, jsObject_, prototype); +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h new file mode 100644 index 0000000000..bed127e5f6 --- /dev/null +++ b/bridge/bindings/qjs/script_wrappable.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_SCRIPT_WRAPPABLE_H +#define KRAKENBRIDGE_SCRIPT_WRAPPABLE_H + +#include +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "foundation/macros.h" +#include "wrapper_type_info.h" + +namespace kraken { + +class ScriptValue; +class GCVisitor; + +// Defines |GetWrapperTypeInfo| virtual method which returns the WrapperTypeInfo +// of the instance. Also declares a static member of type WrapperTypeInfo, of +// which the definition is given by the IDL code generator. +// +// All the derived classes of ScriptWrappable, regardless of directly or +// indirectly, must write this macro in the class definition as long as the +// class has a corresponding .idl file. +#define DEFINE_WRAPPERTYPEINFO() \ + public: \ + const WrapperTypeInfo* GetWrapperTypeInfo() const override { return &wrapper_type_info_; } \ + static const WrapperTypeInfo* GetStaticWrapperTypeInfo() { return &wrapper_type_info_; } \ + \ + private: \ + static const WrapperTypeInfo& wrapper_type_info_ + +// ScriptWrappable provides a way to map from/to C++ DOM implementation to/from +// JavaScript object (platform object). ToQuickJS() converts a ScriptWrappable to +// a QuickJS object and toScriptWrappable() converts a QuickJS object back to +// a ScriptWrappable. +class ScriptWrappable : public GarbageCollected { + public: + ScriptWrappable() = delete; + + explicit ScriptWrappable(JSContext* ctx); + virtual ~ScriptWrappable() = default; + + // Returns the WrapperTypeInfo of the instance. + virtual const WrapperTypeInfo* GetWrapperTypeInfo() const = 0; + + void Trace(GCVisitor* visitor) const override{}; + + JSValue ToQuickJS() const; + JSValue ToQuickJSUnsafe() const; + + ScriptValue ToValue(); + FORCE_INLINE ExecutingContext* GetExecutingContext() const { return context_; }; + FORCE_INLINE JSContext* ctx() const { return ctx_; } + FORCE_INLINE JSRuntime* runtime() const { return runtime_; } + + void InitializeQuickJSObject() override; + + private: + JSValue jsObject_{JS_NULL}; + JSContext* ctx_{nullptr}; + ExecutingContext* context_{nullptr}; + JSRuntime* runtime_{nullptr}; + friend class GCVisitor; +}; + +// Converts a QuickJS object back to a ScriptWrappable. +template +inline ScriptWrappable* toScriptWrappable(JSValue object) { + return static_cast(JS_GetOpaque(object, JSValueGetClassId(object))); +} + +template +Local::~Local() { + if (raw_ == nullptr) + return; + auto* wrappable = To(raw_); + // Record the free operation to avoid JSObject had been freed immediately. + if (LIKELY(wrappable->GetExecutingContext()->HasMutationScope())) { + wrappable->GetExecutingContext()->mutationScope()->RecordFree(wrappable); + } else { + assert_m(false, "LocalHandle must be used after MemberMutationScope allcated."); + } +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_SCRIPT_WRAPPABLE_H diff --git a/bridge/bindings/qjs/source_location.cc b/bridge/bindings/qjs/source_location.cc new file mode 100644 index 0000000000..2cd5561492 --- /dev/null +++ b/bridge/bindings/qjs/source_location.cc @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "source_location.h" + +namespace kraken { + +std::unique_ptr SourceLocation::Capture(const std::string& url, + unsigned int line_number, + unsigned int column_number) { + return std::make_unique(url, line_number, column_number); +} + +SourceLocation::SourceLocation(const std::string& url, unsigned int line_number, unsigned int column_number) + : url_(url), line_number_(line_number), column_number_(column_number) {} + +SourceLocation::~SourceLocation() {} + +} // namespace kraken diff --git a/bridge/bindings/qjs/source_location.h b/bridge/bindings/qjs/source_location.h new file mode 100644 index 0000000000..792b2e43a1 --- /dev/null +++ b/bridge/bindings/qjs/source_location.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ + +#include +#include + +namespace kraken { + +class ExecutingContext; + +class SourceLocation { + public: + // Zero lineNumber and columnNumber mean unknown. Captures current stack + // trace. + static std::unique_ptr Capture(const std::string& url, unsigned line_number, unsigned column_number); + + SourceLocation(const std::string& url, unsigned line_number, unsigned column_number); + ~SourceLocation(); + + const std::string& Url() const { return url_; } + unsigned LineNumber() const { return line_number_; } + unsigned ColumnNumber() const { return column_number_; } + + private: + std::string url_; + unsigned line_number_; + unsigned column_number_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ diff --git a/bridge/bindings/qjs/to_quickjs.h b/bridge/bindings/qjs/to_quickjs.h new file mode 100644 index 0000000000..78f332753d --- /dev/null +++ b/bridge/bindings/qjs/to_quickjs.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ + +#include +#include +#include "core/fileapi/array_buffer_data.h" +#include "native_string_utils.h" +#include "qjs_engine_patch.h" +#include "script_wrappable.h" + +namespace kraken { + +// Arithmetic values +inline JSValue toQuickJS(JSContext* ctx, double v) { + return JS_NewFloat64(ctx, v); +} +inline JSValue toQuickJS(JSContext* ctx, int32_t v) { + return JS_NewInt32(ctx, v); +} +inline JSValue toQuickJS(JSContext* ctx, uint32_t v) { + return JS_NewUint32(ctx, v); +} +inline JSValue toQuickJS(JSContext* ctx, ExceptionState& exception_state) { + return exception_state.ToQuickJS(); +}; + +// String +inline JSValue toQuickJS(JSContext* ctx, const std::string& str) { + return JS_NewString(ctx, str.c_str()); +} +inline JSValue toQuickJS(JSContext* ctx, const char* str) { + return JS_NewString(ctx, str); +} +inline JSValue toQuickJS(JSContext* ctx, std::unique_ptr& str) { + return JS_NewUnicodeString(ctx, str->string(), str->length()); +} +inline JSValue toQuickJS(JSContext* ctx, NativeString* str) { + return JS_NewUnicodeString(ctx, str->string(), str->length()); +} + +// ScriptWrapper +inline JSValue toQuickJS(JSContext* ctx, ScriptWrappable* wrapper) { + return wrapper->ToQuickJS(); +} +inline JSValue toQuickJS(JSContext* ctx, ArrayBufferData data) { + return JS_NewArrayBufferCopy(ctx, data.buffer, data.length); +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h new file mode 100644 index 0000000000..2113a06c44 --- /dev/null +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_WRAPPER_TYPE_INFO_H +#define KRAKENBRIDGE_WRAPPER_TYPE_INFO_H + +#include +#include +#include "bindings/qjs/qjs_engine_patch.h" + +namespace kraken { + +// Define all built-in wrapper class id. +enum { + JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, + JS_CLASS_BLOB, + JS_CLASS_EVENT, + JS_CLASS_ERROR_EVENT, + JS_CLASS_MESSAGE_EVENT, + JS_CLASS_EVENT_TARGET, + JS_CLASS_WINDOW, + JS_CLASS_NODE, + JS_CLASS_ELEMENT, + JS_CLASS_SCREEN, + JS_CLASS_DOCUMENT, + JS_CLASS_CHARACTER_DATA, + JS_CLASS_TEXT, + JS_CLASS_COMMENT, + JS_CLASS_NODE_LIST, + JS_CLASS_DOCUMENT_FRAGMENT, + JS_CLASS_BOUNDING_CLIENT_RECT, + JS_CLASS_ELEMENT_ATTRIBUTES, + JS_CLASS_HTML_ELEMENT, + JS_CLASS_HTML_DIV_ELEMENT, + JS_CLASS_HTML_BODY_ELEMENT, + JS_CLASS_HTML_HEAD_ELEMENT, + JS_CLASS_HTML_HTML_ELEMENT, + JS_CLASS_HTML_TEMPLATE_ELEMENT, + JS_CLASS_HTML_UNKNOWN_ELEMENT, + JS_CLASS_CSS_STYLE_DECLARATION, + + JS_CLASS_CUSTOM_CLASS_INIT_COUNT /* last entry for predefined classes */ +}; + +// Callback when get property using index. +// exp: obj[0] +using IndexedPropertyGetterHandler = JSValue (*)(JSContext* ctx, JSValue obj, uint32_t index); + +// Callback when get property using string or symbol. +// exp: obj['hello'] +using StringPropertyGetterHandler = JSValue (*)(JSContext* ctx, JSValue obj, JSAtom atom); + +// Callback when set property using index. +// exp: obj[0] = value; +using IndexedPropertySetterHandler = bool (*)(JSContext* ctx, JSValueConst obj, uint32_t index, JSValueConst value); + +// Callback when set property using string or symbol. +// exp: obj['hello'] = value; +using StringPropertySetterHandler = bool (*)(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value); + +// Callback when check property exist on object. +// exp: 'hello' in obj; +using StringPropertyCheckerHandler = bool (*)(JSContext* ctx, JSValueConst obj, JSAtom atom); + +// This struct provides a way to store a bunch of information that is helpful +// when creating quickjs objects. Each quickjs bindings class has exactly one static +// WrapperTypeInfo member, so comparing pointers is a safe way to determine if +// types match. +class WrapperTypeInfo final { + public: + bool equals(const WrapperTypeInfo* that) const { return this == that; } + + bool isSubclass(const WrapperTypeInfo* that) const { + for (const WrapperTypeInfo* current = this; current; current = current->parent_class) { + if (current == that) + return true; + } + return false; + } + + JSClassID classId{0}; + const char* className{nullptr}; + const WrapperTypeInfo* parent_class{nullptr}; + JSClassCall* callFunc{nullptr}; + IndexedPropertyGetterHandler indexed_property_getter_handler_{nullptr}; + IndexedPropertySetterHandler indexed_property_setter_handler_{nullptr}; + StringPropertyGetterHandler string_property_getter_handler_{nullptr}; + StringPropertySetterHandler string_property_setter_handler_{nullptr}; + StringPropertyCheckerHandler string_property_checker_handler_{nullptr}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_WRAPPER_TYPE_INFO_H diff --git a/bridge/core/built_in_string.json5 b/bridge/core/built_in_string.json5 new file mode 100644 index 0000000000..0134d23d7e --- /dev/null +++ b/bridge/core/built_in_string.json5 @@ -0,0 +1,236 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "built_in_string" + } + ] + }, + "data": [ + ["null", "null"], + ["false", "false"], + ["true", "true"], + ["if", "if"], + ["else", "else"], + ["return", "return"], + ["var", "var"], + ["this", "this"], + ["delete", "delete"], + ["void", "void"], + ["typeof", "typeof"], + ["new", "new"], + ["in", "in"], + ["instanceof", "instanceof"], + ["do", "do"], + ["while", "while"], + ["for", "for"], + ["break", "break"], + ["continue", "continue"], + ["switch", "switch"], + ["case", "case"], + ["default", "default"], + ["throw", "throw"], + ["try", "try"], + ["catch", "catch"], + ["finally", "finally"], + ["function", "function"], + ["debugger", "debugger"], + ["with", "with"], + ["class", "class"], + ["const", "const"], + ["enum", "enum"], + ["export", "export"], + ["extends", "extends"], + ["import", "import"], + ["super", "super"], + ["implements", "implements"], + ["interface", "interface"], + ["let", "let"], + ["package", "package"], + ["private", "private"], + ["protected", "protected"], + ["public", "public"], + ["static", "static"], + ["yield", "yield"], + ["await", "await"], + ["empty_string", ""], + ["length", "length"], + ["fileName", "fileName"], + ["lineNumber", "lineNumber"], + ["message", "message"], + ["errors", "errors"], + ["stack", "stack"], + ["name", "name"], + ["toString", "toString"], + ["toLocaleString", "toLocaleString"], + ["valueOf", "valueOf"], + ["eval", "eval"], + ["prototype", "prototype"], + ["constructor", "constructor"], + ["configurable", "configurable"], + ["writable", "writable"], + ["enumerable", "enumerable"], + ["value", "value"], + ["get", "get"], + ["set", "set"], + ["of", "of"], + ["__proto__", "__proto__"], + ["undefined", "undefined"], + ["number", "number"], + ["boolean", "boolean"], + ["string", "string"], + ["object", "object"], + ["symbol", "symbol"], + ["integer", "integer"], + ["unknown", "unknown"], + ["arguments", "arguments"], + ["callee", "callee"], + ["caller", "caller"], + ["_eval_", ""], + ["_ret_", ""], + ["_var_", ""], + ["_arg_var_", ""], + ["_with_", ""], + ["lastIndex", "lastIndex"], + ["target", "target"], + ["index", "index"], + ["input", "input"], + ["defineProperties", "defineProperties"], + ["apply", "apply"], + ["join", "join"], + ["concat", "concat"], + ["split", "split"], + ["construct", "construct"], + ["getPrototypeOf", "getPrototypeOf"], + ["setPrototypeOf", "setPrototypeOf"], + ["isExtensible", "isExtensible"], + ["preventExtensions", "preventExtensions"], + ["has", "has"], + ["deleteProperty", "deleteProperty"], + ["defineProperty", "defineProperty"], + ["getOwnPropertyDescriptor", "getOwnPropertyDescriptor"], + ["ownKeys", "ownKeys"], + ["add", "add"], + ["done", "done"], + ["next", "next"], + ["values", "values"], + ["source", "source"], + ["flags", "flags"], + ["global", "global"], + ["unicode", "unicode"], + ["raw", "raw"], + ["new_target", "new.target"], + ["this_active_func", "this.active_func"], + ["home_object", ""], + ["computed_field", ""], + ["static_computed_field", ""], + ["class_fields_init", ""], + ["brand", ""], + ["hash_constructor", "#constructor"], + ["as", "as"], + ["from", "from"], + ["meta", "meta"], + ["_default_", "*default*"], + ["_star_", "*"], + ["Module", "Module"], + ["then", "then"], + ["resolve", "resolve"], + ["reject", "reject"], + ["promise", "promise"], + ["proxy", "proxy"], + ["revoke", "revoke"], + ["async", "async"], + ["exec", "exec"], + ["groups", "groups"], + ["status", "status"], + ["reason", "reason"], + ["globalThis", "globalThis"], + ["bigint", "bigint"], + ["bigfloat", "bigfloat"], + ["bigdecimal", "bigdecimal"], + ["roundingMode", "roundingMode"], + ["maximumSignificantDigits", "maximumSignificantDigits"], + ["maximumFractionDigits", "maximumFractionDigits"], + ["not_equal", "not-equal"], + ["timed_out", "timed-out"], + ["ok", "ok"], + ["toJSON", "toJSON"], + ["Object", "Object"], + ["Array", "Array"], + ["Error", "Error"], + ["Number", "Number"], + ["String", "String"], + ["Boolean", "Boolean"], + ["Symbol", "Symbol"], + ["Arguments", "Arguments"], + ["Math", "Math"], + ["JSON", "JSON"], + ["Date", "Date"], + ["Function", "Function"], + ["GeneratorFunction", "GeneratorFunction"], + ["ForInIterator", "ForInIterator"], + ["RegExp", "RegExp"], + ["ArrayBuffer", "ArrayBuffer"], + ["SharedArrayBuffer", "SharedArrayBuffer"], + ["Uint8ClampedArray", "Uint8ClampedArray"], + ["Int8Array", "Int8Array"], + ["Uint8Array", "Uint8Array"], + ["Int16Array", "Int16Array"], + ["Uint16Array", "Uint16Array"], + ["Int32Array", "Int32Array"], + ["Uint32Array", "Uint32Array"], + ["BigInt64Array", "BigInt64Array"], + ["BigUint64Array", "BigUint64Array"], + ["Float32Array", "Float32Array"], + ["Float64Array", "Float64Array"], + ["DataView", "DataView"], + ["BigInt", "BigInt"], + ["BigFloat", "BigFloat"], + ["BigFloatEnv", "BigFloatEnv"], + ["BigDecimal", "BigDecimal"], + ["OperatorSet", "OperatorSet"], + ["Operators", "Operators"], + ["Map", "Map"], + ["Set", "Set"], + ["WeakMap", "WeakMap"], + ["WeakSet", "WeakSet"], + ["Map_Iterator", "Map Iterator"], + ["Set_Iterator", "Set Iterator"], + ["Array_Iterator", "Array Iterator"], + ["String_Iterator", "String Iterator"], + ["RegExp_String_Iterator", "RegExp String Iterator"], + ["Generator", "Generator"], + ["Proxy", "Proxy"], + ["Promise", "Promise"], + ["PromiseResolveFunction", "PromiseResolveFunction"], + ["PromiseRejectFunction", "PromiseRejectFunction"], + ["AsyncFunction", "AsyncFunction"], + ["AsyncFunctionResolve", "AsyncFunctionResolve"], + ["AsyncFunctionReject", "AsyncFunctionReject"], + ["AsyncGeneratorFunction", "AsyncGeneratorFunction"], + ["AsyncGenerator", "AsyncGenerator"], + ["EvalError", "EvalError"], + ["RangeError", "RangeError"], + ["ReferenceError", "ReferenceError"], + ["SyntaxError", "SyntaxError"], + ["TypeError", "TypeError"], + ["URIError", "URIError"], + ["InternalError", "InternalError"], + ["Private_brand", ""], + ["Symbol_toPrimitive", "Symbol.toPrimitive"], + ["Symbol_iterator", "Symbol.iterator"], + ["Symbol_match", "Symbol.match"], + ["Symbol_matchAll", "Symbol.matchAll"], + ["Symbol_replace", "Symbol.replace"], + ["Symbol_search", "Symbol.search"], + ["Symbol_split", "Symbol.split"], + ["Symbol_toStringTag", "Symbol.toStringTag"], + ["Symbol_isConcatSpreadable", "Symbol.isConcatSpreadable"], + ["Symbol_hasInstance", "Symbol.hasInstance"], + ["Symbol_species", "Symbol.species"], + ["Symbol_unscopables", "Symbol.unscopables"], + ["Symbol_asyncIterator", "Symbol.asyncIterator"], + ["Symbol_operatorSet", "Symbol.operatorSet"] + ] +} diff --git a/bridge/core/css/legacy/css_style_declaration.cc b/bridge/core/css/legacy/css_style_declaration.cc new file mode 100644 index 0000000000..3163f091ec --- /dev/null +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "css_style_declaration.h" +#include +#include "core/dom/element.h" +#include "core/executing_context.h" + +namespace kraken { + +template +inline bool isASCIILower(CharacterType character) { + return character >= 'a' && character <= 'z'; +} + +template +inline CharacterType toASCIIUpper(CharacterType character) { + return character & ~(isASCIILower(character) << 5); +} + +static std::string parseJavaScriptCSSPropertyName(std::string& propertyName) { + static std::unordered_map propertyCache{}; + + if (propertyCache.count(propertyName) > 0) { + return propertyCache[propertyName]; + } + + std::vector buffer(propertyName.size() + 1); + + size_t hyphen = 0; + for (size_t i = 0; i < propertyName.size(); ++i) { + char c = propertyName[i + hyphen]; + if (!c) + break; + if (c == '-') { + hyphen++; + buffer[i] = toASCIIUpper(propertyName[i + hyphen]); + } else { + buffer[i] = c; + } + } + + buffer.emplace_back('\0'); + + std::string result = std::string(buffer.data()); + + propertyCache[propertyName] = result; + return result; +} + +CSSStyleDeclaration* CSSStyleDeclaration::Create(ExecutingContext* context, ExceptionState& exception_state) { + exception_state.ThrowException(context->ctx(), ErrorType::TypeError, "Illegal constructor."); + return nullptr; +} + +CSSStyleDeclaration::CSSStyleDeclaration(ExecutingContext* context, int64_t owner_element_target_id) + : ScriptWrappable(context->ctx()), owner_element_target_id_(owner_element_target_id) {} + +AtomicString CSSStyleDeclaration::item(const AtomicString& key, ExceptionState& exception_state) { + std::string propertyName = key.ToStdString(); + return InternalGetPropertyValue(propertyName); +} + +bool CSSStyleDeclaration::SetItem(const AtomicString& key, const AtomicString& value, ExceptionState& exception_state) { + std::string propertyName = key.ToStdString(); + return InternalSetProperty(propertyName, value); +} + +int64_t CSSStyleDeclaration::length() const { + return properties_.size(); +} + +AtomicString CSSStyleDeclaration::getPropertyValue(const AtomicString& key, ExceptionState& exception_state) { + std::string propertyName = key.ToStdString(); + return InternalGetPropertyValue(propertyName); +} + +void CSSStyleDeclaration::setProperty(const AtomicString& key, + const AtomicString& value, + ExceptionState& exception_state) { + std::string propertyName = key.ToStdString(); + InternalSetProperty(propertyName, value); +} + +AtomicString CSSStyleDeclaration::removeProperty(const AtomicString& key, ExceptionState& exception_state) { + std::string propertyName = key.ToStdString(); + return InternalRemoveProperty(propertyName); +} + +void CSSStyleDeclaration::CopyWith(CSSStyleDeclaration* inline_style) { + for (auto& attr : inline_style->properties_) { + properties_[attr.first] = attr.second; + } +} + +std::string CSSStyleDeclaration::ToString() const { + if (properties_.empty()) + return ""; + + std::string s; + + for (auto& attr : properties_) { + s += attr.first + ": " + attr.second.ToStdString() + ";"; + } + + s += "\""; + return s; +} + +AtomicString CSSStyleDeclaration::InternalGetPropertyValue(std::string& name) { + name = parseJavaScriptCSSPropertyName(name); + + if (LIKELY(properties_.count(name) > 0)) { + return properties_[name]; + } + + return AtomicString::Empty(ctx()); +} + +bool CSSStyleDeclaration::InternalSetProperty(std::string& name, const AtomicString& value) { + name = parseJavaScriptCSSPropertyName(name); + + if (properties_[name] == value) { + return true; + } + + properties_[name] = value; + + std::unique_ptr args_01 = stringToNativeString(name); + std::unique_ptr args_02 = value.ToNativeString(); + GetExecutingContext()->uiCommandBuffer()->addCommand(owner_element_target_id_, UICommand::kSetStyle, + std::move(args_01), std::move(args_02), nullptr); + + return true; +} + +AtomicString CSSStyleDeclaration::InternalRemoveProperty(std::string& name) { + name = parseJavaScriptCSSPropertyName(name); + + if (UNLIKELY(properties_.count(name) == 0)) { + return AtomicString::Empty(ctx()); + } + + AtomicString return_value = properties_[name]; + properties_.erase(name); + + std::unique_ptr args_01 = stringToNativeString(name); + std::unique_ptr args_02 = jsValueToNativeString(ctx(), JS_NULL); + GetExecutingContext()->uiCommandBuffer()->addCommand(owner_element_target_id_, UICommand::kSetStyle, + std::move(args_01), std::move(args_02), nullptr); + + return return_value; +} + +} // namespace kraken diff --git a/bridge/core/css/legacy/css_style_declaration.d.ts b/bridge/core/css/legacy/css_style_declaration.d.ts new file mode 100644 index 0000000000..25cf36f1d3 --- /dev/null +++ b/bridge/core/css/legacy/css_style_declaration.d.ts @@ -0,0 +1,14 @@ +export interface CSSStyleDeclaration { + // @ts-ignore + readonly length: int64; + // @ts-ignore + getPropertyValue(property: string): string; + // @ts-ignore + setProperty(property: string, value: string): void; + // @ts-ignore + removeProperty(property: string): string; + + [prop: string]: string; + + new(): void; +} diff --git a/bridge/core/css/legacy/css_style_declaration.h b/bridge/core/css/legacy/css_style_declaration.h new file mode 100644 index 0000000000..2c51ded1cf --- /dev/null +++ b/bridge/core/css/legacy/css_style_declaration.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CSS_STYLE_DECLARATION_H +#define KRAKENBRIDGE_CSS_STYLE_DECLARATION_H + +#include +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/cppgc/member.h" +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/script_value.h" +#include "bindings/qjs/script_wrappable.h" + +namespace kraken { + +class Element; + +class CSSStyleDeclaration : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = CSSStyleDeclaration*; + static CSSStyleDeclaration* Create(ExecutingContext* context, ExceptionState& exception_state); + explicit CSSStyleDeclaration(ExecutingContext* context, int64_t owner_element_target_id); + + AtomicString item(const AtomicString& key, ExceptionState& exception_state); + bool SetItem(const AtomicString& key, const AtomicString& value, ExceptionState& exception_state); + int64_t length() const; + + AtomicString getPropertyValue(const AtomicString& key, ExceptionState& exception_state); + void setProperty(const AtomicString& key, const AtomicString& value, ExceptionState& exception_state); + AtomicString removeProperty(const AtomicString& key, ExceptionState& exception_state); + + void CopyWith(CSSStyleDeclaration* attributes); + + std::string ToString() const; + + private: + AtomicString InternalGetPropertyValue(std::string& name); + bool InternalSetProperty(std::string& name, const AtomicString& value); + AtomicString InternalRemoveProperty(std::string& name); + std::unordered_map properties_; + int32_t owner_element_target_id_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CSS_STYLE_DECLARATION_H diff --git a/bridge/bindings/qjs/dom/text_node_test.cc b/bridge/core/css/legacy/css_style_declaration_test.cc similarity index 57% rename from bridge/bindings/qjs/dom/text_node_test.cc rename to bridge/core/css/legacy/css_style_declaration_test.cc index d7552d3b71..02f2539949 100644 --- a/bridge/bindings/qjs/dom/text_node_test.cc +++ b/bridge/core/css/legacy/css_style_declaration_test.cc @@ -1,29 +1,27 @@ /* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. */ -#include "event_target.h" #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "page.h" -TEST(TextNode, instanceofNode) { +using namespace kraken; + +TEST(CSSStyleDeclaration, setStyleData) { bool static errorCalled = false; bool static logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; - EXPECT_STREQ(message.c_str(), "true true"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = - "let text = document.createTextNode('1234');" - "console.log(text instanceof Node, text instanceof Text);"; + "document.documentElement.style.backgroundColor = 'white';" + "document.documentElement.style.backgroundColor = 'white';"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); } diff --git a/bridge/core/css_property_list.h b/bridge/core/css_property_list.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bridge/include/dart_methods.h b/bridge/core/dart_methods.h similarity index 67% rename from bridge/include/dart_methods.h rename to bridge/core/dart_methods.h index dd8c519744..93a21a65a3 100644 --- a/bridge/include/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -5,21 +5,27 @@ #ifndef KRAKEN_DART_METHODS_H_ #define KRAKEN_DART_METHODS_H_ -#include "kraken_bridge.h" +/// Functions implements at dart side, including timer, Rendering and module API. +/// Communicate via Dart FFI. #include #include -#define KRAKEN_EXPORT __attribute__((__visibility__("default"))) +#include "foundation/native_string.h" -struct NativeString; -struct NativeScreen; +namespace kraken { 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 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); +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 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); @@ -27,16 +33,22 @@ typedef int32_t (*SetInterval)(void* callbackContext, int32_t contextId, AsyncCa typedef int32_t (*RequestAnimationFrame)(void* callbackContext, int32_t contextId, AsyncRAFCallback callback); typedef void (*ClearTimeout)(int32_t contextId, int32_t timerId); typedef void (*CancelAnimationFrame)(int32_t contextId, int32_t id); -typedef NativeScreen* (*GetScreen)(int32_t contextId); -typedef void (*ToBlob)(void* callbackContext, int32_t contextId, AsyncBlobCallback blobCallback, int32_t elementId, double devicePixelRatio); +typedef void (*ToBlob)(void* callbackContext, + int32_t contextId, + AsyncBlobCallback blobCallback, + int32_t elementId, + double devicePixelRatio); typedef void (*OnJSError)(int32_t contextId, const char*); typedef void (*OnJSLog)(int32_t contextId, int32_t level, const char*); -typedef void (*FlushUICommand)(); -typedef void (*InitWindow)(int32_t contextId, void* nativePtr); -typedef void (*InitDocument)(int32_t contextId, void* nativePtr); +typedef void (*FlushUICommand)(int32_t contextId); using MatchImageSnapshotCallback = void (*)(void* callbackContext, int32_t contextId, int8_t, const char* errmsg); -using MatchImageSnapshot = void (*)(void* callbackContext, int32_t contextId, uint8_t* bytes, int32_t length, NativeString* name, MatchImageSnapshotCallback callback); +using MatchImageSnapshot = void (*)(void* callbackContext, + int32_t contextId, + uint8_t* bytes, + int32_t length, + NativeString* name, + MatchImageSnapshotCallback callback); using Environment = const char* (*)(); #if ENABLE_PROFILE @@ -56,7 +68,6 @@ struct MousePointer { using SimulatePointer = void (*)(MousePointer**, int32_t length, int32_t pointer); using SimulateInputText = void (*)(NativeString* nativeString); -namespace kraken { struct DartMethodPointer { DartMethodPointer() = default; InvokeModule invokeModule{nullptr}; @@ -67,7 +78,6 @@ struct DartMethodPointer { ClearTimeout clearTimeout{nullptr}; RequestAnimationFrame requestAnimationFrame{nullptr}; CancelAnimationFrame cancelAnimationFrame{nullptr}; - GetScreen getScreen{nullptr}; ToBlob toBlob{nullptr}; OnJSError onJsError{nullptr}; OnJSLog onJsLog{nullptr}; @@ -79,20 +89,8 @@ struct DartMethodPointer { #if ENABLE_PROFILE GetPerformanceEntries getPerformanceEntries{nullptr}; #endif - InitWindow initWindow{nullptr}; - InitDocument initDocument{nullptr}; }; -void registerDartMethods(uint64_t* methodBytes, int32_t length); - -#ifdef IS_TEST -KRAKEN_EXPORT -void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length); -#endif - -KRAKEN_EXPORT -std::shared_ptr getDartMethod(); - } // namespace kraken #endif diff --git a/bridge/core/dom/binding_call_methods.json5 b/bridge/core/dom/binding_call_methods.json5 new file mode 100644 index 0000000000..3065d28e7b --- /dev/null +++ b/bridge/core/dom/binding_call_methods.json5 @@ -0,0 +1,36 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "binding_call_methods" + } + ] + }, + "data": [ + "click", + "scroll", + "scrollBy", + "clientTop", + "clientLeft", + "clientWidth", + "clientHeight", + "scrollLeft", + "scrollTop", + "getBoundingClientRect", + ["getPropertyMagic", "%g"], + ["setPropertyMagic", "%s"], + "open", + "devicePixelRatio", + "colorScheme", + "scrollX", + "scrollY", + "innerWidth", + "innerHeight", + "availWidth", + "availHeight", + "width", + "height", + "screen" + ] +} diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/dom/binding_object.cc new file mode 100644 index 0000000000..6d39a9249c --- /dev/null +++ b/bridge/core/dom/binding_object.cc @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "binding_object.h" +#include "binding_call_methods.h" +#include "bindings/qjs/exception_state.h" +#include "core/executing_context.h" +#include "foundation/logging.h" + +namespace kraken { + +void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_object, + NativeValue* return_value, + NativeString* method, + int32_t argc, + NativeValue* argv) { + NativeValue result = binding_object->binding_target_->HandleCallFromDartSide(method, argc, argv); + if (return_value != nullptr) + *return_value = result; +} + +BindingObject::BindingObject(ExecutingContext* context) : context_(context) {} +BindingObject::~BindingObject() { + delete binding_object_; +} + +void BindingObject::BindDartObject(NativeBindingObject* native_binding_object) { + native_binding_object->binding_target_ = this; + native_binding_object->invoke_binding_methods_from_dart = NativeBindingObject::HandleCallFromDartSide; + binding_object_ = native_binding_object; +} + +NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, + int32_t argc, + const NativeValue* argv, + ExceptionState& exception_state) const { + if (binding_object_->invoke_bindings_methods_from_native == nullptr) { + exception_state.ThrowException(context_->ctx(), ErrorType::InternalError, + "Failed to call dart method: invokeBindingMethod not initialized."); + return Native_NewNull(); + } + + KRAKEN_LOG(VERBOSE) << " binding object_ " << &binding_object_; + NativeValue return_value = Native_NewNull(); + binding_object_->invoke_bindings_methods_from_native(binding_object_, &return_value, + method.ToNativeString().release(), argc, argv); + return return_value; +} + +NativeValue BindingObject::GetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const { + context_->FlushUICommand(); + const NativeValue argv[] = {Native_NewString(prop.ToNativeString().release())}; + return InvokeBindingMethod(binding_call_methods::kgetPropertyMagic, 1, argv, exception_state); +} + +NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, + NativeValue value, + ExceptionState& exception_state) const { + context_->FlushUICommand(); + const NativeValue argv[] = {Native_NewString(prop.ToNativeString().release()), value}; + return InvokeBindingMethod(binding_call_methods::ksetPropertyMagic, 2, argv, exception_state); +} + +} // namespace kraken diff --git a/bridge/core/dom/binding_object.h b/bridge/core/dom/binding_object.h new file mode 100644 index 0000000000..682582396b --- /dev/null +++ b/bridge/core/dom/binding_object.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_BINDING_OBJECT_H_ +#define KRAKENBRIDGE_CORE_DOM_BINDING_OBJECT_H_ + +#include +#include "bindings/qjs/atomic_string.h" +#include "foundation/native_value.h" + +namespace kraken { + +class BindingObject; +class NativeBindingObject; +class ExceptionState; + +using InvokeBindingsMethodsFromNative = void (*)(const NativeBindingObject* binding_object, + NativeValue* return_value, + NativeString* method, + int32_t argc, + const NativeValue* argv); + +using InvokeBindingMethodsFromDart = void (*)(NativeBindingObject* binding_object, + NativeValue* return_value, + NativeString* method, + int32_t argc, + NativeValue* argv); + +struct NativeBindingObject { + NativeBindingObject() = delete; + explicit NativeBindingObject(BindingObject* target) + : binding_target_(target), invoke_binding_methods_from_dart(HandleCallFromDartSide){}; + + static void HandleCallFromDartSide(NativeBindingObject* binding_object, + NativeValue* return_value, + NativeString* method, + int32_t argc, + NativeValue* argv); + + BindingObject* binding_target_{nullptr}; + InvokeBindingMethodsFromDart invoke_binding_methods_from_dart{nullptr}; + InvokeBindingsMethodsFromNative invoke_bindings_methods_from_native{nullptr}; +}; + +class BindingObject { + public: + using ImplType = BindingObject*; + BindingObject() = delete; + ~BindingObject(); + explicit BindingObject(ExecutingContext* context); + + // Handle call from dart side. + virtual NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const = 0; + // Invoke methods which implemented at dart side. + NativeValue InvokeBindingMethod(const AtomicString& method, + int32_t argc, + const NativeValue* args, + ExceptionState& exception_state) const; + NativeValue GetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const; + NativeValue SetBindingProperty(const AtomicString& prop, NativeValue value, ExceptionState& exception_state) const; + + const NativeBindingObject* bindingObject() const { return binding_object_; } + + protected: + // NativeBindingObject may allocated at Dart side. Binding this with Dart allocated NativeBindingObject. + void BindDartObject(NativeBindingObject* native_binding_object); + + private: + ExecutingContext* context_{nullptr}; + NativeBindingObject* binding_object_{new NativeBindingObject(this)}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_BINDING_OBJECT_H_ diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc new file mode 100644 index 0000000000..54b1bdd6dc --- /dev/null +++ b/bridge/core/dom/character_data.cc @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "character_data.h" +#include "core/dom/document.h" + +namespace kraken { + +void CharacterData::setData(const AtomicString& data) { + data_ = data; +} + +std::string CharacterData::nodeValue() const { + return data_.ToStdString(); +} +CharacterData::CharacterData(TreeScope& tree_scope, const AtomicString& text, Node::ConstructionType type) + : Node(tree_scope.GetDocument().GetExecutingContext(), &tree_scope, type), + data_(!text.IsNull() ? text : AtomicString::Empty(ctx())) { + assert(type == kCreateOther || type == kCreateText); +} + +} // namespace kraken diff --git a/bridge/core/dom/character_data.d.ts b/bridge/core/dom/character_data.d.ts new file mode 100644 index 0000000000..b0a6ef5309 --- /dev/null +++ b/bridge/core/dom/character_data.d.ts @@ -0,0 +1,7 @@ +import {Node} from "./node"; + +export interface CharacterData extends Node { + readonly data: string; + readonly length: int64; + new(): void; +} diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h new file mode 100644 index 0000000000..1a4edacc99 --- /dev/null +++ b/bridge/core/dom/character_data.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CHARACTER_DATA_H +#define KRAKENBRIDGE_CHARACTER_DATA_H + +#include "node.h" + +namespace kraken { + +class Document; + +class CharacterData : public Node { + DEFINE_WRAPPERTYPEINFO(); + + public: + const AtomicString& data() const { return data_; } + int64_t length() const { return data_.length(); }; + void setData(const AtomicString& data); + + std::string nodeValue() const override; + + protected: + CharacterData(TreeScope& tree_scope, const AtomicString& text, ConstructionType type); + + private: + AtomicString data_; +}; + +template <> +struct DowncastTraits { + static bool AllowFrom(const Node& node) { return node.IsCharacterDataNode(); } +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CHARACTER_DATA_H diff --git a/bridge/core/dom/child_node_list.cc b/bridge/core/dom/child_node_list.cc new file mode 100644 index 0000000000..c29722e6f7 --- /dev/null +++ b/bridge/core/dom/child_node_list.cc @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "child_node_list.h" +#include "bindings/qjs/cppgc/gc_visitor.h" + +namespace kraken { + +ChildNodeList::ChildNodeList(ContainerNode* parent) : parent_(parent), NodeList(parent->ctx()) {} +ChildNodeList::~ChildNodeList() = default; + +Node* ChildNodeList::VirtualOwnerNode() const { + return &OwnerNode(); +} + +Node* ChildNodeList::item(unsigned index, ExceptionState& exception_state) const { + return collection_index_cache_.NodeAt(*this, index); +} + +Node* ChildNodeList::TraverseForwardToOffset(unsigned offset, Node& current_node, unsigned& current_offset) const { + assert(current_offset < offset); + assert(OwnerNode().childNodes() == this); + assert(&OwnerNode() == current_node.parentNode()); + for (Node* next = current_node.nextSibling(); next; next = next->nextSibling()) { + if (++current_offset == offset) + return next; + } + return nullptr; +} + +Node* ChildNodeList::TraverseBackwardToOffset(unsigned offset, Node& current_node, unsigned& current_offset) const { + assert(current_offset > offset); + assert(OwnerNode().childNodes() == this); + assert(&OwnerNode() == current_node.parentNode()); + for (Node* previous = current_node.previousSibling(); previous; previous = previous->previousSibling()) { + if (--current_offset == offset) + return previous; + } + return nullptr; +} + +void ChildNodeList::Trace(GCVisitor* visitor) const { + visitor->Trace(parent_); + collection_index_cache_.Trace(visitor); + NodeList::Trace(visitor); +} + +} // namespace kraken diff --git a/bridge/core/dom/child_node_list.h b/bridge/core/dom/child_node_list.h new file mode 100644 index 0000000000..8abed0ddad --- /dev/null +++ b/bridge/core/dom/child_node_list.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ +#define KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ + +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "core/dom/collection_index_cache.h" +#include "core/dom/container_node.h" +#include "core/dom/node_list.h" + +namespace kraken { + +class ExceptionState; + +class ChildNodeList : public NodeList { + public: + explicit ChildNodeList(ContainerNode* root_node); + ~ChildNodeList() override; + + // DOM API. + unsigned length() const override { return collection_index_cache_.NodeCount(*this); } + + Node* item(unsigned index, ExceptionState& exception_state) const override; + + // Non-DOM API. + void InvalidateCache() { collection_index_cache_.Invalidate(); } + ContainerNode& OwnerNode() const { return *parent_.Get(); } + + ContainerNode& RootNode() const { return OwnerNode(); } + + // CollectionIndexCache API. + bool CanTraverseBackward() const { return true; } + Node* TraverseToFirst() const { return RootNode().firstChild(); } + Node* TraverseToLast() const { return RootNode().lastChild(); } + Node* TraverseForwardToOffset(unsigned offset, Node& current_node, unsigned& current_offset) const; + Node* TraverseBackwardToOffset(unsigned offset, Node& current_node, unsigned& current_offset) const; + + void Trace(GCVisitor*) const override; + + private: + bool IsChildNodeList() const override { return true; } + Node* VirtualOwnerNode() const override; + + Member parent_; + mutable CollectionIndexCache collection_index_cache_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ diff --git a/bridge/core/dom/collection_index_cache.h b/bridge/core/dom/collection_index_cache.h new file mode 100644 index 0000000000..63241e50c8 --- /dev/null +++ b/bridge/core/dom/collection_index_cache.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ +#define KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ + +#include +#include +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "foundation/macros.h" + +namespace kraken { + +template +class CollectionIndexCache { + KRAKEN_DISALLOW_NEW(); + + public: + CollectionIndexCache(); + + bool IsEmpty(const Collection& collection) { + if (IsCachedNodeCountValid()) + return !CachedNodeCount(); + if (CachedNode()) + return false; + return !NodeAt(collection, 0); + } + + bool HasExactlyOneNode(const Collection& collection) { + if (IsCachedNodeCountValid()) + return CachedNodeCount() == 1; + if (CachedNode()) + return !CachedNodeIndex() && !NodeAt(collection, 1); + return NodeAt(collection, 0) && !NodeAt(collection, 1); + } + + unsigned NodeCount(const Collection&); + NodeType* NodeAt(const Collection&, unsigned index); + + void Invalidate(); + + void NodeInserted(); + void NodeRemoved(); + + virtual void Trace(GCVisitor* visitor) const { visitor->Trace(current_node_); } + + protected: + FORCE_INLINE NodeType* CachedNode() const { return current_node_.Get(); } + FORCE_INLINE unsigned CachedNodeIndex() const { + assert(CachedNode()); + return cached_node_index_; + } + FORCE_INLINE void SetCachedNode(NodeType* node, unsigned index) { + assert(node); + current_node_ = node; + cached_node_index_ = index; + } + + FORCE_INLINE bool IsCachedNodeCountValid() const { return is_length_cache_valid_; } + FORCE_INLINE unsigned CachedNodeCount() const { return cached_node_count_; } + FORCE_INLINE void SetCachedNodeCount(unsigned length) { + cached_node_count_ = length; + is_length_cache_valid_ = true; + } + + private: + NodeType* NodeBeforeCachedNode(const Collection&, unsigned index); + NodeType* NodeAfterCachedNode(const Collection&, unsigned index); + + Member current_node_; + unsigned cached_node_count_; + unsigned cached_node_index_ : 31; + unsigned is_length_cache_valid_ : 1; +}; + +template +CollectionIndexCache::CollectionIndexCache() + : current_node_(nullptr), cached_node_count_(0), cached_node_index_(0), is_length_cache_valid_(false) {} + +template +void CollectionIndexCache::Invalidate() { + current_node_ = nullptr; + is_length_cache_valid_ = false; +} + +template +void CollectionIndexCache::NodeInserted() { + cached_node_count_++; + current_node_ = nullptr; +} + +template +void CollectionIndexCache::NodeRemoved() { + cached_node_count_--; + current_node_ = nullptr; +} + +template +inline unsigned CollectionIndexCache::NodeCount(const Collection& collection) { + if (IsCachedNodeCountValid()) + return CachedNodeCount(); + + NodeAt(collection, UINT_MAX); + assert(IsCachedNodeCountValid()); + + return CachedNodeCount(); +} + +template +inline NodeType* CollectionIndexCache::NodeAt(const Collection& collection, unsigned index) { + if (IsCachedNodeCountValid() && index >= CachedNodeCount()) + return nullptr; + + if (CachedNode()) { + if (index > CachedNodeIndex()) + return NodeAfterCachedNode(collection, index); + if (index < CachedNodeIndex()) + return NodeBeforeCachedNode(collection, index); + return CachedNode(); + } + + // No valid cache yet, let's find the first matching element. + NodeType* first_node = collection.TraverseToFirst(); + if (!first_node) { + // The collection is empty. + SetCachedNodeCount(0); + return nullptr; + } + SetCachedNode(first_node, 0); + return index ? NodeAfterCachedNode(collection, index) : first_node; +} + +template +inline NodeType* CollectionIndexCache::NodeBeforeCachedNode(const Collection& collection, + unsigned index) { + assert(CachedNode()); // Cache should be valid. + unsigned current_index = CachedNodeIndex(); + assert(current_index > index); + + // Determine if we should traverse from the beginning of the collection + // instead of the cached node. + bool first_is_closer = index < current_index - index; + if (first_is_closer || !collection.CanTraverseBackward()) { + NodeType* first_node = collection.TraverseToFirst(); + assert(first_node); + SetCachedNode(first_node, 0); + return index ? NodeAfterCachedNode(collection, index) : first_node; + } + + // Backward traversal from the cached node to the requested index. + assert(collection.CanTraverseBackward()); + NodeType* current_node = collection.TraverseBackwardToOffset(index, *CachedNode(), current_index); + assert(current_node); + SetCachedNode(current_node, current_index); + return current_node; +} + +template +inline NodeType* CollectionIndexCache::NodeAfterCachedNode(const Collection& collection, + unsigned index) { + assert(CachedNode()); // Cache should be valid. + unsigned current_index = CachedNodeIndex(); + assert(current_index < index); + + // Determine if we should traverse from the end of the collection instead of + // the cached node. + bool last_is_closer = IsCachedNodeCountValid() && CachedNodeCount() - index < index - current_index; + if (last_is_closer && collection.CanTraverseBackward()) { + NodeType* last_item = collection.TraverseToLast(); + assert(last_item); + SetCachedNode(last_item, CachedNodeCount() - 1); + if (index < CachedNodeCount() - 1) + return NodeBeforeCachedNode(collection, index); + return last_item; + } + + // Forward traversal from the cached node to the requested index. + NodeType* current_node = collection.TraverseForwardToOffset(index, *CachedNode(), current_index); + if (!current_node) { + // Did not find the node. On plus side, we now know the length. + if (IsCachedNodeCountValid()) + assert(current_index + 1 == CachedNodeCount()); + SetCachedNodeCount(current_index + 1); + return nullptr; + } + SetCachedNode(current_node, current_index); + return current_node; +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ diff --git a/bridge/core/dom/comment.cc b/bridge/core/dom/comment.cc new file mode 100644 index 0000000000..db84b3f304 --- /dev/null +++ b/bridge/core/dom/comment.cc @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "comment.h" +#include "built_in_string.h" +#include "document.h" +#include "tree_scope.h" + +namespace kraken { + +Comment* Comment::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected(*context->document(), ConstructionType::kCreateOther); +} + +Comment* Comment::Create(Document& document) { + return MakeGarbageCollected(document, ConstructionType::kCreateOther); +} + +Comment::Comment(TreeScope& tree_scope, ConstructionType type) + : CharacterData(tree_scope, built_in_string::kempty_string, type) { + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateComment, + (void*)bindingObject()); +} + +Node::NodeType Comment::nodeType() const { + return Node::kCommentNode; +} +std::string Comment::nodeName() const { + return "#comment"; +} + +Node* Comment::Clone(Document& factory, CloneChildrenFlag flag) const { + return Create(factory); +} + +} // namespace kraken diff --git a/bridge/core/dom/comment.d.ts b/bridge/core/dom/comment.d.ts new file mode 100644 index 0000000000..60461cb888 --- /dev/null +++ b/bridge/core/dom/comment.d.ts @@ -0,0 +1,5 @@ +import {CharacterData} from "./character_data"; + +export interface Comment extends CharacterData { + new(): Comment; +} diff --git a/bridge/core/dom/comment.h b/bridge/core/dom/comment.h new file mode 100644 index 0000000000..5156573832 --- /dev/null +++ b/bridge/core/dom/comment.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_COMMENT_H +#define KRAKENBRIDGE_COMMENT_H + +#include "character_data.h" + +namespace kraken { + +class Comment : public CharacterData { + DEFINE_WRAPPERTYPEINFO(); + + public: + static Comment* Create(ExecutingContext* context, ExceptionState& exception_state); + static Comment* Create(Document&); + + explicit Comment(TreeScope& tree_scope, ConstructionType type); + + NodeType nodeType() const override; + + private: + std::string nodeName() const override; + Node* Clone(Document&, CloneChildrenFlag) const override; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_COMMENT_H diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc new file mode 100644 index 0000000000..b6b161ca97 --- /dev/null +++ b/bridge/core/dom/container_node.cc @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "container_node.h" +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "document.h" +#include "document_fragment.h" +#include "node_traversal.h" + +namespace kraken { + +HTMLCollection* ContainerNode::Children() { + // TODO: add children implements. + return nullptr; +} + +unsigned ContainerNode::CountChildren() const { + unsigned count = 0; + for (Node* node = firstChild(); node; node = node->nextSibling()) + count++; + return count; +} + +inline void GetChildNodes(ContainerNode& node, NodeVector& nodes) { + assert(!nodes.size()); + for (Node* child = node.firstChild(); child; child = child->nextSibling()) + nodes.push_back(child); +} + +class ContainerNode::AdoptAndInsertBefore { + public: + inline void operator()(ContainerNode& container, Node& child, Node* next) const { + assert(next); + assert(next->parentNode() == &container); + container.InsertBeforeCommon(*next, child); + } +}; + +class ContainerNode::AdoptAndAppendChild { + public: + inline void operator()(ContainerNode& container, Node& child, Node*) const { container.AppendChildCommon(child); } +}; + +bool ContainerNode::IsChildTypeAllowed(const Node& child) const { + auto* child_fragment = DynamicTo(child); + if (!child_fragment) + return ChildTypeAllowed(child.nodeType()); + + for (Node* node = child_fragment->firstChild(); node; node = node->nextSibling()) { + if (!ChildTypeAllowed(node->nodeType())) + return false; + } + return true; +} + +// Returns true if |new_child| contains this node. In that case, +// |exception_state| has an exception. +// https://dom.spec.whatwg.org/#concept-tree-host-including-inclusive-ancestor +bool ContainerNode::IsHostIncludingInclusiveAncestorOfThis(const Node& new_child, + ExceptionState& exception_state) const { + // Non-ContainerNode can contain nothing. + if (!new_child.IsContainerNode()) + return false; + + bool child_contains_parent = false; + const Node& root = TreeRoot(); + auto* fragment = DynamicTo(root); + if (fragment && fragment->IsTemplateContent()) { + child_contains_parent = new_child.ContainsIncludingHostElements(*this); + } else { + child_contains_parent = new_child.contains(this, exception_state); + } + if (child_contains_parent) { + exception_state.ThrowException(ctx(), ErrorType::TypeError, "The new child element contains the parent."); + } + return child_contains_parent; +} + +inline bool CheckReferenceChildParent(const Node& parent, + const Node* next, + const Node* old_child, + ExceptionState& exception_state) { + if (next && next->parentNode() != &parent) { + exception_state.ThrowException(next->ctx(), ErrorType::TypeError, + "The node before which the new node is " + "to be inserted is not a child of this " + "node."); + return false; + } + if (old_child && old_child->parentNode() != &parent) { + exception_state.ThrowException(old_child->ctx(), ErrorType::TypeError, + "The node to be replaced is not a child of this node."); + return false; + } + return true; +} + +// This dispatches various events; DOM mutation events, blur events, IFRAME +// unload events, etc. +// Returns true if DOM mutation should be proceeded. +static inline bool CollectChildrenAndRemoveFromOldParent(Node& node, + NodeVector& nodes, + ExceptionState& exception_state) { + if (auto* fragment = DynamicTo(node)) { + GetChildNodes(*fragment, nodes); + fragment->RemoveChildren(); + return !nodes.empty(); + } + nodes.push_back(&node); + if (ContainerNode* old_parent = node.parentNode()) + old_parent->RemoveChild(&node, exception_state); + return !exception_state.HasException() && !nodes.empty(); +} + +Node* ContainerNode::InsertBefore(Node* new_child, Node* ref_child, ExceptionState& exception_state) { + assert(new_child); + // https://dom.spec.whatwg.org/#concept-node-pre-insert + + // insertBefore(node, null) is equivalent to appendChild(node) + if (!ref_child) + return AppendChild(new_child, exception_state); + + // 1. Ensure pre-insertion validity of node into parent before child. + if (!EnsurePreInsertionValidity(*new_child, ref_child, nullptr, exception_state)) + return new_child; + + // 2. Let reference child be child. + // 3. If reference child is node, set it to node’s next sibling. + if (ref_child == new_child) { + ref_child = new_child->nextSibling(); + if (!ref_child) + return AppendChild(new_child, exception_state); + } + + // 4. Adopt node into parent’s node document. + NodeVector targets; + targets.reserve(kInitialNodeVectorSize); + if (!CollectChildrenAndRemoveFromOldParent(*new_child, targets, exception_state)) + return new_child; + + // 5. Insert node into parent before reference child. + NodeVector post_insertion_notification_targets; + { InsertNodeVector(targets, ref_child, AdoptAndInsertBefore(), &post_insertion_notification_targets); } + return new_child; +} + +Node* ContainerNode::ReplaceChild(Node* new_child, Node* old_child, ExceptionState& exception_state) { + assert(new_child); + // https://dom.spec.whatwg.org/#concept-node-replace + + if (!old_child) { + exception_state.ThrowException(new_child->ctx(), ErrorType::TypeError, "The node to be replaced is null."); + return nullptr; + } + + // Step 2 to 6. + if (!EnsurePreInsertionValidity(*new_child, nullptr, old_child, exception_state)) + return old_child; + + // 7. Let reference child be child’s next sibling. + Node* next = old_child->nextSibling(); + // 8. If reference child is node, set it to node’s next sibling. + if (next == new_child) + next = new_child->nextSibling(); + + // 10. Adopt node into parent’s node document. + // Though the following CollectChildrenAndRemoveFromOldParent() also calls + // RemoveChild(), we'd like to call RemoveChild() here to make a separated + // MutationRecord. + if (ContainerNode* new_child_parent = new_child->parentNode()) { + new_child_parent->RemoveChild(new_child, exception_state); + if (exception_state.HasException()) + return nullptr; + } + + NodeVector targets; + targets.reserve(kInitialNodeVectorSize); + NodeVector post_insertion_notification_targets; + post_insertion_notification_targets.reserve(kInitialNodeVectorSize); + { + // 9. Let previousSibling be child’s previous sibling. + // 11. Let removedNodes be the empty list. + // 15. Queue a mutation record of "childList" for target parent with + // addedNodes nodes, removedNodes removedNodes, nextSibling reference child, + // and previousSibling previousSibling. + + // 12. If child’s parent is not null, run these substeps: + // 1. Set removedNodes to a list solely containing child. + // 2. Remove child from its parent with the suppress observers flag set. + if (ContainerNode* old_child_parent = old_child->parentNode()) { + old_child_parent->RemoveChild(old_child, exception_state); + if (exception_state.HasException()) + return nullptr; + } + + // 13. Let nodes be node’s children if node is a DocumentFragment node, and + // a list containing solely node otherwise. + if (!CollectChildrenAndRemoveFromOldParent(*new_child, targets, exception_state)) + return old_child; + // 10. Adopt node into parent’s node document. + // 14. Insert node into parent before reference child with the suppress + // observers flag set. + if (next) { + InsertNodeVector(targets, next, AdoptAndInsertBefore(), &post_insertion_notification_targets); + } else { + InsertNodeVector(targets, nullptr, AdoptAndAppendChild(), &post_insertion_notification_targets); + } + } + + // 16. Return child. + return old_child; +} + +Node* ContainerNode::RemoveChild(Node* old_child, ExceptionState& exception_state) { + // NotFoundError: Raised if oldChild is not a child of this node. + if (!old_child || old_child->parentNode() != this) { + exception_state.ThrowException(ctx(), ErrorType::TypeError, "The node to be removed is not a child of this node."); + return nullptr; + } + + Node* child = old_child; + + // Events fired when blurring currently focused node might have moved this + // child into a different parent. + if (child->parentNode() != this) { + exception_state.ThrowException(ctx(), ErrorType::TypeError, + "The node to be removed is no longer a " + "child of this node. Perhaps it was moved " + "in a 'blur' event handler?"); + return nullptr; + } + + { + Node* prev = child->previousSibling(); + Node* next = child->nextSibling(); + { RemoveBetween(prev, next, *child); } + } + return child; +} + +Node* ContainerNode::AppendChild(Node* new_child, ExceptionState& exception_state) { + assert(new_child); + // Make sure adding the new child is ok + if (!EnsurePreInsertionValidity(*new_child, nullptr, nullptr, exception_state)) + return new_child; + + NodeVector targets; + targets.reserve(kInitialNodeVectorSize); + if (!CollectChildrenAndRemoveFromOldParent(*new_child, targets, exception_state)) + return new_child; + + NodeVector post_insertion_notification_targets; + post_insertion_notification_targets.reserve(kInitialNodeVectorSize); + { InsertNodeVector(targets, nullptr, AdoptAndAppendChild(), &post_insertion_notification_targets); } + return new_child; +} + +Node* ContainerNode::AppendChild(Node* new_child) { + return AppendChild(new_child, ASSERT_NO_EXCEPTION()); +} + +bool ContainerNode::EnsurePreInsertionValidity(const Node& new_child, + const Node* next, + const Node* old_child, + ExceptionState& exception_state) const { + assert(!(next && old_child)); + + // Use common case fast path if possible. + if ((new_child.IsElementNode() || new_child.IsTextNode()) && IsElementNode()) { + assert(IsChildTypeAllowed(new_child)); + // 2. If node is a host-including inclusive ancestor of parent, throw a + // HierarchyRequestError. + if (IsHostIncludingInclusiveAncestorOfThis(new_child, exception_state)) + return false; + // 3. If child is not null and its parent is not parent, then throw a + // NotFoundError. + return CheckReferenceChildParent(*this, next, old_child, exception_state); + } + + // if (auto* document = DynamicTo(this)) { + // // Step 2 is unnecessary. No one can have a Document child. + // // Step 3: + // if (!CheckReferenceChildParent(*this, next, old_child, exception_state)) + // return false; + // // Step 4-6. + // return document->CanAcceptChild(new_child, next, old_child, exception_state); + // } + + // 2. If node is a host-including inclusive ancestor of parent, throw a + // HierarchyRequestError. + if (IsHostIncludingInclusiveAncestorOfThis(new_child, exception_state)) + return false; + + // 3. If child is not null and its parent is not parent, then throw a + // NotFoundError. + if (!CheckReferenceChildParent(*this, next, old_child, exception_state)) + return false; + + // 4. If node is not a DocumentFragment, DocumentType, Element, Text, + // ProcessingInstruction, or Comment node, throw a HierarchyRequestError. + // 5. If either node is a Text node and parent is a document, or node is a + // doctype and parent is not a document, throw a HierarchyRequestError. + if (!IsChildTypeAllowed(new_child)) { + exception_state.ThrowException( + ctx(), ErrorType::TypeError, + "Nodes of type '" + new_child.nodeName() + "' may not be inserted inside nodes of type '" + nodeName() + "'."); + return false; + } + + // Step 6 is unnecessary for non-Document nodes. + return true; +} + +void ContainerNode::RemoveChildren() { + if (!first_child_) + return; + + while (Node* child = first_child_) { + RemoveBetween(nullptr, child->nextSibling(), *child); + } +} + +void ContainerNode::CloneChildNodesFrom(const ContainerNode& node, CloneChildrenFlag flag) { + assert(flag != CloneChildrenFlag::kSkip); + for (const Node& child : NodeTraversal::ChildrenOf(node)) { + AppendChild(child.Clone(GetDocument(), flag)); + } +} + +std::string ContainerNode::nodeValue() const { + return ""; +} + +ContainerNode::ContainerNode(TreeScope* tree_scope, ConstructionType type) + : ContainerNode(tree_scope->GetDocument().GetExecutingContext(), &tree_scope->GetDocument(), type) {} +ContainerNode::ContainerNode(ExecutingContext* context, Document* document, ConstructionType type) + : Node(context, document, type), first_child_(nullptr), last_child_(nullptr) {} + +void ContainerNode::RemoveBetween(Node* previous_child, Node* next_child, Node& old_child) { + assert(old_child.parentNode() == this); + + if (next_child) + next_child->SetPreviousSibling(previous_child); + if (previous_child) + previous_child->SetNextSibling(next_child); + if (first_child_ == &old_child) + SetFirstChild(next_child); + if (last_child_ == &old_child) + SetLastChild(previous_child); + + old_child.SetPreviousSibling(nullptr); + old_child.SetNextSibling(nullptr); + old_child.SetParentOrShadowHostNode(nullptr); + + GetExecutingContext()->uiCommandBuffer()->addCommand(old_child.eventTargetId(), UICommand::kRemoveNode, nullptr); +} + +template +void ContainerNode::InsertNodeVector(const NodeVector& targets, + Node* next, + const Functor& mutator, + NodeVector* post_insertion_notification_targets) { + assert(post_insertion_notification_targets); + { + for (const auto& target_node : targets) { + assert(target_node); + assert(!target_node->parentNode()); + Node& child = *target_node; + mutator(*this, child, next); + } + } +} + +void ContainerNode::InsertBeforeCommon(Node& next_child, Node& new_child) { + // Use insertBefore if you need to handle reparenting (and want DOM mutation + // events). + assert(!new_child.parentNode()); + assert(!new_child.nextSibling()); + assert(!new_child.previousSibling()); + + Node* prev = next_child.previousSibling(); + assert(last_child_ != prev); + next_child.SetPreviousSibling(&new_child); + if (prev) { + assert(firstChild() != &next_child); + assert(prev->nextSibling() == &next_child); + prev->SetNextSibling(&new_child); + } else { + assert(firstChild() == &next_child); + SetFirstChild(&new_child); + } + new_child.SetParentOrShadowHostNode(this); + new_child.SetPreviousSibling(prev); + new_child.SetNextSibling(&next_child); + + std::unique_ptr args_01 = stringToNativeString(std::to_string(new_child.eventTargetId())); + std::unique_ptr args_02 = stringToNativeString("beforebegin"); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kInsertAdjacentNode, + std::move(args_01), std::move(args_02), nullptr); +} + +void ContainerNode::AppendChildCommon(Node& child) { + child.SetParentOrShadowHostNode(this); + if (last_child_) { + child.SetPreviousSibling(last_child_); + last_child_->SetNextSibling(&child); + } else { + SetFirstChild(&child); + } + SetLastChild(&child); + + std::unique_ptr args_01 = stringToNativeString(std::to_string(child.eventTargetId())); + std::unique_ptr args_02 = stringToNativeString("beforeend"); + + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kInsertAdjacentNode, + std::move(args_01), std::move(args_02), nullptr); +} + +void ContainerNode::NotifyNodeInserted(Node& root) { + NotifyNodeInsertedInternal(root); +} + +void ContainerNode::NotifyNodeInsertedInternal(Node& root) { + for (Node& node : NodeTraversal::InclusiveDescendantsOf(root)) { + // As an optimization we don't notify leaf nodes when when inserting + // into detached subtrees that are not in a shadow tree. + if (!isConnected() && !node.IsContainerNode()) + continue; + } +} + +void ContainerNode::NotifyNodeRemoved(Node& root) { + for (Node& node : NodeTraversal::InclusiveDescendantsOf(root)) { + // As an optimization we skip notifying Text nodes and other leaf nodes + // of removal when they're not in the Document tree and not in a shadow root + // since the virtual call to removedFrom is not needed. + if (!node.IsContainerNode() && !node.IsInTreeScope()) + continue; + node.RemovedFrom(*this); + } +} + +void ContainerNode::Trace(GCVisitor* visitor) const { + visitor->Trace(first_child_); + visitor->Trace(last_child_); + + Node::Trace(visitor); +} + +} // namespace kraken diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h new file mode 100644 index 0000000000..eedeffe055 --- /dev/null +++ b/bridge/core/dom/container_node.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ +#define KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ + +#include +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "node.h" +#include "node_list.h" + +namespace kraken { + +class HTMLCollection; + +// This constant controls how much buffer is initially allocated +// for a Node Vector that is used to store child Nodes of a given Node. +const int kInitialNodeVectorSize = 11; +using NodeVector = std::vector; + +class ContainerNode : public Node { + public: + Node* firstChild() const { return first_child_.Get(); } + Node* lastChild() const { return last_child_.Get(); } + bool hasChildren() const { return first_child_.Get(); } + bool HasChildren() const { return first_child_.Get(); } + + bool HasOneChild() const { return first_child_ && !first_child_->nextSibling(); } + bool HasOneTextChild() const { return HasOneChild() && first_child_->IsTextNode(); } + bool HasChildCount(unsigned) const; + + HTMLCollection* Children(); + + unsigned CountChildren() const; + + Node* InsertBefore(Node* new_child, Node* ref_child, ExceptionState&); + Node* ReplaceChild(Node* new_child, Node* old_child, ExceptionState&); + Node* RemoveChild(Node* child, ExceptionState&); + Node* AppendChild(Node* new_child, ExceptionState&); + Node* AppendChild(Node* new_child); + bool EnsurePreInsertionValidity(const Node& new_child, + const Node* next, + const Node* old_child, + ExceptionState&) const; + + void RemoveChildren(); + + void CloneChildNodesFrom(const ContainerNode&, CloneChildrenFlag); + + std::string nodeValue() const override; + + virtual bool ChildrenCanHaveStyle() const { return true; } + + void Trace(GCVisitor* visitor) const override; + + protected: + ContainerNode(TreeScope* tree_scope, ConstructionType = kCreateContainer); + ContainerNode(ExecutingContext* context, Document* document, ConstructionType = kCreateContainer); + + void SetFirstChild(Node* child) { first_child_ = child; } + void SetLastChild(Node* child) { last_child_ = child; } + + private: + bool IsContainerNode() const = delete; // This will catch anyone doing an unnecessary check. + bool IsTextNode() const = delete; // This will catch anyone doing an unnecessary check. + void RemoveBetween(Node* previous_child, Node* next_child, Node& old_child); + // Inserts the specified nodes before |next|. + // |next| may be nullptr. + // |post_insertion_notification_targets| must not be nullptr. + template + void InsertNodeVector(const NodeVector&, Node* next, const Functor&, NodeVector* post_insertion_notification_targets); + + class AdoptAndInsertBefore; + class AdoptAndAppendChild; + friend class AdoptAndInsertBefore; + friend class AdoptAndAppendChild; + + void InsertBeforeCommon(Node& next_child, Node& new_child); + void AppendChildCommon(Node& child); + + void NotifyNodeInserted(Node&); + void NotifyNodeInsertedInternal(Node&); + void NotifyNodeRemoved(Node&); + + inline bool IsChildTypeAllowed(const Node& child) const; + inline bool IsHostIncludingInclusiveAncestorOfThis(const Node&, ExceptionState&) const; + + Member first_child_; + Member last_child_; +}; + +inline Node* Node::firstChild() const { + auto* this_node = DynamicTo(this); + if (!this_node) + return nullptr; + return this_node->firstChild(); +} + +inline Node* Node::lastChild() const { + auto* this_node = DynamicTo(this); + if (!this_node) { + return nullptr; + } + return this_node->lastChild(); +} + +inline bool ContainerNode::HasChildCount(unsigned count) const { + Node* child = first_child_.Get(); + while (count && child) { + child = child->nextSibling(); + --count; + } + return !count && !child; +} + +template <> +struct DowncastTraits { + static bool AllowFrom(const Node& node) { return node.IsContainerNode(); } +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc new file mode 100644 index 0000000000..7697721dad --- /dev/null +++ b/bridge/core/dom/document.cc @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "document.h" +#include "bindings/qjs/exception_message.h" +#include "core/dom/element.h" +#include "core/html/html_body_element.h" +#include "core/html/html_element.h" +#include "core/html/html_head_element.h" +#include "core/html/html_html_element.h" +#include "core/html/html_unknown_element.h" +#include "element_traversal.h" +#include "foundation/ascii_types.h" +#include "html_element_factory.h" + +namespace kraken { + +Document* Document::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected(context); +} + +Document::Document(ExecutingContext* context) + : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) { + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateDocument, + (void*)bindingObject()); + document_element_ = MakeGarbageCollected(*this); +} + +Element* Document::createElement(const AtomicString& name, ExceptionState& exception_state) { + if (!IsValidName(name)) { + exception_state.ThrowException(ctx(), ErrorType::InternalError, + "The tag name provided ('" + name.ToStdString() + "') is not a valid name."); + return nullptr; + } + + if (auto* element = HTMLElementFactory::Create(name, *this)) { + return element; + } + + return MakeGarbageCollected(name, *this); +} + +Text* Document::createTextNode(const AtomicString& value, ExceptionState& exception_state) { + return Text::Create(*this, value); +} + +DocumentFragment* Document::createDocumentFragment(ExceptionState& exception_state) { + return DocumentFragment::Create(*this); +} + +Comment* Document::createComment(ExceptionState& exception_state) { + return Comment::Create(*this); +} + +std::string Document::nodeName() const { + return "#document"; +} + +std::string Document::nodeValue() const { + return ""; +} + +Node::NodeType Document::nodeType() const { + return kDocumentNode; +} + +bool Document::ChildTypeAllowed(NodeType type) const { + switch (type) { + case kAttributeNode: + case kDocumentFragmentNode: + case kDocumentNode: + case kTextNode: + return false; + case kCommentNode: + return true; + case kDocumentTypeNode: + case kElementNode: + // Documents may contain no more than one of each of these. + // (One Element and one DocumentType.) + for (Node& c : NodeTraversal::ChildrenOf(*this)) { + if (c.nodeType() == type) + return false; + } + return true; + } + return false; +} + +template +static inline bool IsValidNameASCII(const CharType* characters, unsigned length) { + CharType c = characters[0]; + if (!(IsASCIIAlpha(c) || c == ':' || c == '_')) + return false; + + for (unsigned i = 1; i < length; ++i) { + c = characters[i]; + if (!(IsASCIIAlphanumeric(c) || c == ':' || c == '_' || c == '-' || c == '.')) + return false; + } + + return true; +} + +bool Document::IsValidName(const AtomicString& name) { + unsigned length = name.length(); + if (!length) + return false; + + auto string_view = name.ToStringView(); + + if (string_view.Is8Bit()) { + const char* characters = string_view.Characters8(); + if (IsValidNameASCII(characters, length)) { + return true; + } + } + + const char16_t* characters = string_view.Characters16(); + + if (IsValidNameASCII(characters, length)) { + return true; + } + + return false; +} + +Node* Document::Clone(Document&, CloneChildrenFlag) const { + assert(false); + return nullptr; +} + +void Document::InitDocumentElement() { + ExceptionState exception_state; + AppendChild(document_element_, exception_state); +} + +// Legacy impl: Get the JS polyfill impl from global object. +ScriptValue Document::location() const { + JSValue location = JS_GetPropertyStr(ctx(), GetExecutingContext()->Global(), "location"); + ScriptValue result = ScriptValue(ctx(), location); + JS_FreeValue(ctx(), location); + return result; +} + +HTMLBodyElement* Document::body() const { + if (!IsA(documentElement())) + return nullptr; + + for (HTMLElement* child = Traversal::FirstChild(*documentElement()); child; + child = Traversal::NextSibling(*child)) { + if (IsA(*child)) + return DynamicTo(child); + } + + return nullptr; +} + +void Document::setBody(HTMLBodyElement* new_body, ExceptionState& exception_state) { + if (!new_body) { + exception_state.ThrowException(ctx(), ErrorType::TypeError, + ExceptionMessage::ArgumentNullOrIncorrectType(1, "HTMLBodyElement")); + return; + } + + if (!documentElement()) { + exception_state.ThrowException(ctx(), ErrorType::TypeError, "No document element exists."); + return; + } + + if (!IsA(*new_body)) { + exception_state.ThrowException(ctx(), ErrorType::TypeError, + "The new body element is of type '" + new_body->tagName().ToStdString() + + "'. It must be either a 'BODY' element."); + return; + } + + HTMLElement* old_body = body(); + if (old_body == new_body) + return; + + if (old_body) + documentElement()->ReplaceChild(new_body, old_body, exception_state); + else + documentElement()->AppendChild(new_body, exception_state); +} + +HTMLHeadElement* Document::head() const { + Node* de = documentElement(); + if (de == nullptr) + return nullptr; + + return Traversal::FirstChild(*de); +} + +uint32_t Document::RequestAnimationFrame(const std::shared_ptr& callback, + ExceptionState& exception_state) { + return script_animation_controller_.RegisterFrameCallback(callback, exception_state); +} + +void Document::CancelAnimationFrame(uint32_t request_id, ExceptionState& exception_state) { + script_animation_controller_.CancelFrameCallback(GetExecutingContext(), request_id, exception_state); +} + +void Document::Trace(GCVisitor* visitor) const { + visitor->Trace(document_element_); + script_animation_controller_.Trace(visitor); + ContainerNode::Trace(visitor); +} + +} // namespace kraken diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts new file mode 100644 index 0000000000..975d358613 --- /dev/null +++ b/bridge/core/dom/document.d.ts @@ -0,0 +1,23 @@ +import {Node} from "./node"; +import {Text} from "./text"; +import {Comment} from "./comment"; +import {DocumentFragment} from "./document_fragment"; +import {HTMLHeadElement} from "../html/html_head_element"; +import {HTMLBodyElement} from "../html/html_body_element"; +import {HTMLHtmlElement} from "../html/html_html_element"; +import {Element} from "./element"; + +interface Document extends Node { + body: HTMLBodyElement | null; + readonly head: HTMLHeadElement | null; + readonly documentElement: HTMLHtmlElement; + // Legacy impl: get the polyfill implements from global object. + readonly location: any; + + createElement(tagName: string): Element; + createTextNode(value: string): Text; + createDocumentFragment(): DocumentFragment; + createComment(): Comment; + + new(): Document; +} diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h new file mode 100644 index 0000000000..38d9b92637 --- /dev/null +++ b/bridge/core/dom/document.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_DOCUMENT_H +#define KRAKENBRIDGE_DOCUMENT_H + +#include "bindings/qjs/cppgc/local_handle.h" +#include "container_node.h" +#include "core/dom/comment.h" +#include "core/dom/document_fragment.h" +#include "core/dom/text.h" +#include "html_element_type_helper.h" +#include "scripted_animation_controller.h" +#include "tree_scope.h" + +namespace kraken { + +class HTMLBodyElement; +class HTMLHeadElement; +class HTMLHtmlElement; + +// A document (https://dom.spec.whatwg.org/#concept-document) is the root node +// of a tree of DOM nodes, generally resulting from the parsing of a markup +// (typically, HTML) resource. +class Document : public ContainerNode, public TreeScope { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = Document*; + + explicit Document(ExecutingContext* context); + + static Document* Create(ExecutingContext* context, ExceptionState& exception_state); + + Element* createElement(const AtomicString& name, ExceptionState& exception_state); + Text* createTextNode(const AtomicString& value, ExceptionState& exception_state); + DocumentFragment* createDocumentFragment(ExceptionState& exception_state); + Comment* createComment(ExceptionState& exception_state); + + [[nodiscard]] std::string nodeName() const override; + [[nodiscard]] std::string nodeValue() const override; + [[nodiscard]] NodeType nodeType() const override; + [[nodiscard]] bool ChildTypeAllowed(NodeType) const override; + + // The following implements the rule from HTML 4 for what valid names are. + static bool IsValidName(const AtomicString& name); + + Node* Clone(Document&, CloneChildrenFlag) const override; + + [[nodiscard]] HTMLHtmlElement* documentElement() const { return DynamicTo(document_element_.Get()); } + void InitDocumentElement(); + + // "body element" as defined by HTML5 + // (https://html.spec.whatwg.org/C/#the-body-element-2). + // That is, the first body or frameset child of the document element. + [[nodiscard]] HTMLBodyElement* body() const; + void setBody(HTMLBodyElement* body, ExceptionState& exception_state); + [[nodiscard]] HTMLHeadElement* head() const; + void setHead(HTMLHeadElement* head, ExceptionState& exception_state); + + ScriptValue location() const; + + void IncrementNodeCount() { node_count_++; } + void DecrementNodeCount() { + assert(node_count_ > 0); + node_count_--; + } + int NodeCount() const { return node_count_; } + + uint32_t RequestAnimationFrame(const std::shared_ptr& callback, ExceptionState& exception_state); + void CancelAnimationFrame(uint32_t request_id, ExceptionState& exception_state); + + void Trace(GCVisitor* visitor) const override; + + private: + int node_count_; + Member document_element_; + ScriptAnimationController script_animation_controller_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_DOCUMENT_H diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc new file mode 100644 index 0000000000..3a36472c53 --- /dev/null +++ b/bridge/core/dom/document_fragment.cc @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "document_fragment.h" +#include "document.h" +#include "events/event_target.h" + +namespace kraken { + +DocumentFragment* DocumentFragment::Create(Document& document) { + return MakeGarbageCollected(&document, ConstructionType::kCreateDocumentFragment); +} + +DocumentFragment* DocumentFragment::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected(context->document(), ConstructionType::kCreateDocumentFragment); +} + +DocumentFragment::DocumentFragment(Document* document, ConstructionType type) : ContainerNode(document, type) {} + +std::string DocumentFragment::nodeName() const { + return "#document-fragment"; +} + +Node::NodeType DocumentFragment::nodeType() const { + return NodeType::kDocumentFragmentNode; +} + +std::string DocumentFragment::nodeValue() const { + return ""; +} + +Node* DocumentFragment::Clone(Document& factory, CloneChildrenFlag flag) const { + DocumentFragment* clone = Create(factory); + if (flag != CloneChildrenFlag::kSkip) + clone->CloneChildNodesFrom(*this, flag); + return clone; +} + +bool DocumentFragment::ChildTypeAllowed(NodeType type) const { + switch (type) { + case kElementNode: + case kCommentNode: + case kTextNode: + return true; + default: + return false; + } +} + +} // namespace kraken diff --git a/bridge/core/dom/document_fragment.d.ts b/bridge/core/dom/document_fragment.d.ts new file mode 100644 index 0000000000..83e1ec38a0 --- /dev/null +++ b/bridge/core/dom/document_fragment.d.ts @@ -0,0 +1,5 @@ +import { Node } from './node'; + +interface DocumentFragment extends Node { + new(): DocumentFragment; +} diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h new file mode 100644 index 0000000000..2815ac17a0 --- /dev/null +++ b/bridge/core/dom/document_fragment.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_DOCUMENT_FRAGMENT_H +#define KRAKENBRIDGE_DOCUMENT_FRAGMENT_H + +#include "container_node.h" + +namespace kraken { + +class DocumentFragment : public ContainerNode { + DEFINE_WRAPPERTYPEINFO(); + + public: + static DocumentFragment* Create(Document& document); + static DocumentFragment* Create(ExecutingContext* context, ExceptionState& exception_state); + + DocumentFragment(Document* document, ConstructionType type); + ~DocumentFragment() override{}; + + virtual bool IsTemplateContent() const { return false; } + + // This will catch anyone doing an unnecessary check. + bool IsDocumentFragment() const = delete; + + std::string nodeValue() const override; + + protected: + std::string nodeName() const final; + + private: + NodeType nodeType() const final; + Node* Clone(Document&, CloneChildrenFlag) const override; + bool ChildTypeAllowed(NodeType) const override; +}; + +template <> +struct DowncastTraits { + static bool AllowFrom(const Node& node) { return node.IsDocumentFragment(); } +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_DOCUMENT_FRAGMENT_H diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc new file mode 100644 index 0000000000..aa6c66356f --- /dev/null +++ b/bridge/core/dom/document_test.cc @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "gtest/gtest.h" +#include "kraken_test_env.h" + +using namespace kraken; + +TEST(Document, createElement) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "
"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let div = document.createElement('div');" + "console.log(div);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Document, body) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), ""); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = "console.log(document.body)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Document, appendParentWillFail) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto context = bridge->GetExecutingContext(); + const char* code = "document.body.appendChild(document.documentElement)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, true); + EXPECT_EQ(logCalled, false); +} + +TEST(Document, createTextNode) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "
"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let div = document.createElement('div');" + "div.setAttribute('hello', 1234);" + "document.body.appendChild(div);" + "let text = document.createTextNode('1234');" + "div.appendChild(text);" + "console.log(div);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Document, createComment) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "
"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let div = document.createElement('div');" + "div.setAttribute('hello', 1234);" + "document.body.appendChild(div);" + "let comment = document.createComment();" + "div.appendChild(comment);" + "console.log(div);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Document, instanceofNode) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "true true true"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "console.log(document instanceof Node, document instanceof Document, document instanceof EventTarget)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Document, FreedByOutOfScope) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = false; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = "(() => { let img = document.createElement('div'); })();"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, false); +} + +TEST(Document, createElementShouldWorkWithMultipleContext) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + + kraken::KrakenPage* bridge1; + + const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; + + { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); + auto context = bridge->GetExecutingContext(); + bridge->evaluateScript(code, strlen(code), "vm://", 0); + bridge1 = bridge.release(); + } + + { + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); + auto context = bridge->GetExecutingContext(); + const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + } + + bridge1->evaluateScript(code, strlen(code), "vm://", 0); + + delete bridge1; +} diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc new file mode 100644 index 0000000000..14a668fe85 --- /dev/null +++ b/bridge/core/dom/element.cc @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "element.h" + +#include +#include "binding_call_methods.h" +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/script_promise.h" +#include "bindings/qjs/script_promise_resolver.h" +#include "core/dom/document_fragment.h" +#include "core/fileapi/blob.h" +#include "core/html/html_template_element.h" +#include "core/html/parser/html_parser.h" +#include "foundation/native_value_converter.h" + +namespace kraken { + +Element::Element(const AtomicString& tag_name, Document* document, Node::ConstructionType construction_type) + : ContainerNode(document, construction_type), tag_name_(tag_name) { + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateElement, + std::move(tag_name.ToNativeString()), (void*)bindingObject()); +} + +ElementAttributes& Element::EnsureElementAttributes() { + if (attributes_ == nullptr) { + attributes_ = ElementAttributes::Create(this); + } + return *attributes_; +} + +bool Element::hasAttribute(const AtomicString& name, ExceptionState& exception_state) { + return EnsureElementAttributes().hasAttribute(name, exception_state); +} + +AtomicString Element::getAttribute(const AtomicString& name, ExceptionState& exception_state) { + return EnsureElementAttributes().GetAttribute(name); +} + +void Element::setAttribute(const AtomicString& name, const AtomicString& value) { + ExceptionState exception_state; + return setAttribute(name, value, exception_state); +} + +void Element::setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state) { + if (EnsureElementAttributes().hasAttribute(name, exception_state)) { + AtomicString&& oldAttribute = EnsureElementAttributes().GetAttribute(name); + if (!EnsureElementAttributes().setAttribute(name, value, exception_state)) { + return; + }; + _didModifyAttribute(name, oldAttribute, value); + } else { + if (!EnsureElementAttributes().setAttribute(name, value, exception_state)) { + return; + }; + _didModifyAttribute(name, AtomicString::Empty(ctx()), value); + } + + std::unique_ptr args_01 = name.ToNativeString(); + std::unique_ptr args_02 = value.ToNativeString(); + + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kSetAttribute, std::move(args_01), + std::move(args_02), nullptr); +} + +void Element::removeAttribute(const AtomicString& name, ExceptionState& exception_state) { + EnsureElementAttributes().removeAttribute(name, exception_state); +} + +BoundingClientRect* Element::getBoundingClientRect(ExceptionState& exception_state) { + GetExecutingContext()->FlushUICommand(); + NativeValue result = InvokeBindingMethod(binding_call_methods::kgetBoundingClientRect, 0, nullptr, exception_state); + return BoundingClientRect::Create( + GetExecutingContext(), + NativeValueConverter>::FromNativeValue(result)); +} + +void Element::click(ExceptionState& exception_state) { + GetExecutingContext()->FlushUICommand(); + InvokeBindingMethod(binding_call_methods::kclick, 0, nullptr, exception_state); +} + +void Element::scroll(ExceptionState& exception_state) { + return scroll(0, 0, exception_state); +} + +void Element::scroll(double x, double y, ExceptionState& exception_state) { + GetExecutingContext()->FlushUICommand(); + const NativeValue args[] = { + NativeValueConverter::ToNativeValue(x), + NativeValueConverter::ToNativeValue(y), + }; + InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); +} + +void Element::scroll(const std::shared_ptr& options, ExceptionState& exception_state) { + GetExecutingContext()->FlushUICommand(); + const NativeValue args[] = { + NativeValueConverter::ToNativeValue(options->left()), + NativeValueConverter::ToNativeValue(options->top()), + }; + InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); +} + +void Element::scrollBy(ExceptionState& exception_state) { + return scrollBy(0, 0, exception_state); +} + +void Element::scrollBy(double x, double y, ExceptionState& exception_state) { + GetExecutingContext()->FlushUICommand(); + const NativeValue args[] = { + NativeValueConverter::ToNativeValue(x), + NativeValueConverter::ToNativeValue(y), + }; + InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, exception_state); +} + +void Element::scrollBy(const std::shared_ptr& options, ExceptionState& exception_state) { + GetExecutingContext()->FlushUICommand(); + const NativeValue args[] = { + NativeValueConverter::ToNativeValue(options->left()), + NativeValueConverter::ToNativeValue(options->top()), + }; + InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, exception_state); +} + +void Element::scrollTo(ExceptionState& exception_state) { + return scroll(exception_state); +} + +void Element::scrollTo(double x, double y, ExceptionState& exception_state) { + return scroll(x, y, exception_state); +} + +void Element::scrollTo(const std::shared_ptr& options, ExceptionState& exception_state) { + return scroll(options, exception_state); +} + +bool Element::HasTagName(const AtomicString& name) const { + return name == tag_name_; +} + +std::string Element::nodeValue() const { + return ""; +} + +std::string Element::nodeName() const { + return tag_name_.ToStdString(); +} + +CSSStyleDeclaration* Element::style() { + if (!IsStyledElement()) + return nullptr; + return &EnsureCSSStyleDeclaration(); +} + +CSSStyleDeclaration& Element::EnsureCSSStyleDeclaration() { + if (cssom_wrapper_ == nullptr) { + cssom_wrapper_ = MakeGarbageCollected(GetExecutingContext(), eventTargetId()); + } + return *cssom_wrapper_; +} + +Element& Element::CloneWithChildren(CloneChildrenFlag flag, Document* document) const { + Element& clone = CloneWithoutAttributesAndChildren(document ? *document : GetDocument()); + assert(IsHTMLElement() == clone.IsHTMLElement()); + + clone.CloneAttributesFrom(*this); + clone.CloneNonAttributePropertiesFrom(*this, flag); + clone.CloneChildNodesFrom(*this, flag); + return clone; +} + +Element& Element::CloneWithoutChildren(Document* document) const { + Element& clone = CloneWithoutAttributesAndChildren(document ? *document : GetDocument()); + + assert(IsHTMLElement() == clone.IsHTMLElement()); + + clone.CloneAttributesFrom(*this); + clone.CloneNonAttributePropertiesFrom(*this, CloneChildrenFlag::kSkip); + return clone; +} + +void Element::CloneAttributesFrom(const Element& other) { + if (other.attributes_ != nullptr) { + EnsureElementAttributes().CopyWith(other.attributes_); + } + if (other.cssom_wrapper_ != nullptr) { + EnsureCSSStyleDeclaration().CopyWith(other.cssom_wrapper_); + } +} + +bool Element::HasEquivalentAttributes(const Element& other) const { + return attributes_ != nullptr && other.attributes_ != nullptr && other.attributes_->IsEquivalent(*attributes_); +} + +void Element::Trace(GCVisitor* visitor) const { + visitor->Trace(attributes_); + visitor->Trace(cssom_wrapper_); + ContainerNode::Trace(visitor); +} + +Node* Element::Clone(Document& factory, CloneChildrenFlag flag) const { + if (flag == CloneChildrenFlag::kSkip) + return &CloneWithoutChildren(&factory); + Element* copy = &CloneWithChildren(flag, &factory); + return copy; +} + +Element& Element::CloneWithoutAttributesAndChildren(Document& factory) const { + return *(factory.createElement(tagName(), ASSERT_NO_EXCEPTION())); +} + +class ElementSnapshotReader { + public: + ElementSnapshotReader(ExecutingContext* context, + Element* element, + std::shared_ptr resolver, + double device_pixel_ratio) + : context_(context), element_(element), resolver_(std::move(resolver)), device_pixel_ratio_(device_pixel_ratio) { + Start(); + }; + + void Start(); + void HandleSnapshot(uint8_t* bytes, int32_t length); + void HandleFailed(const char* error); + + private: + ExecutingContext* context_; + Element* element_; + std::shared_ptr resolver_; + double device_pixel_ratio_; +}; + +void ElementSnapshotReader::Start() { + context_->FlushUICommand(); + + auto callback = [](void* ptr, int32_t contextId, const char* error, uint8_t* bytes, int32_t length) -> void { + auto* reader = static_cast(ptr); + if (error != nullptr) { + reader->HandleFailed(error); + } else { + reader->HandleSnapshot(bytes, length); + } + delete reader; + }; + + context_->dartMethodPtr()->toBlob(this, context_->contextId(), callback, element_->eventTargetId(), + device_pixel_ratio_); +} + +void ElementSnapshotReader::HandleSnapshot(uint8_t* bytes, int32_t length) { + MemberMutationScope mutation_scope{context_}; + Blob* blob = Blob::Create(context_); + blob->AppendBytes(bytes, length); + resolver_->Resolve(blob); +} + +void ElementSnapshotReader::HandleFailed(const char* error) { + MemberMutationScope mutation_scope{context_}; + ExceptionState exception_state; + exception_state.ThrowException(context_->ctx(), ErrorType::InternalError, error); + resolver_->Reject(exception_state); +} + +ScriptPromise Element::toBlob(ExceptionState& exception_state) { + return toBlob(1.0, exception_state); +} + +ScriptPromise Element::toBlob(double device_pixel_ratio, ExceptionState& exception_state) { + auto resolver = ScriptPromiseResolver::Create(GetExecutingContext()); + new ElementSnapshotReader(GetExecutingContext(), this, resolver, device_pixel_ratio); + return resolver->Promise(); +} + +double Element::clientHeight() const { + ExceptionState exception_state; + return NativeValueConverter::FromNativeValue( + GetBindingProperty(binding_call_methods::kclientHeight, exception_state)); +} + +double Element::clientWidth() const { + ExceptionState exception_state; + return NativeValueConverter::FromNativeValue( + GetBindingProperty(binding_call_methods::kclientWidth, exception_state)); +} + +double Element::clientLeft() const { + ExceptionState exception_state; + return NativeValueConverter::FromNativeValue( + GetBindingProperty(binding_call_methods::kclientLeft, exception_state)); +} + +double Element::clientTop() const { + ExceptionState exception_state; + return NativeValueConverter::FromNativeValue( + GetBindingProperty(binding_call_methods::kclientTop, exception_state)); +} + +double Element::scrollTop() const { + ExceptionState exception_state; + return NativeValueConverter::FromNativeValue( + GetBindingProperty(binding_call_methods::kscrollTop, exception_state)); +} + +void Element::setScrollTop(double v, ExceptionState& exception_state) { + SetBindingProperty(binding_call_methods::kscrollTop, NativeValueConverter::ToNativeValue(v), + exception_state); +} + +double Element::scrollLeft() const { + ExceptionState exception_state; + return NativeValueConverter::FromNativeValue( + GetBindingProperty(binding_call_methods::kclientTop, exception_state)); +} + +void Element::setScrollLeft(double v, ExceptionState& exception_state) { + SetBindingProperty(binding_call_methods::kscrollLeft, NativeValueConverter::ToNativeValue(v), + exception_state); +} + +std::string Element::outerHTML() { + std::string s = "<" + tagName().ToStdString(); + + // Read attributes + if (attributes_ != nullptr) { + s += " " + attributes_->ToString(); + } + if (cssom_wrapper_ != nullptr) { + s += " style=\"" + cssom_wrapper_->ToString(); + } + + s += ">"; + + std::string childHTML = innerHTML(); + s += childHTML; + s += ""; + + return s; +} + +std::string Element::innerHTML() { + std::string s; + + // If Element is TemplateElement, the innerHTML content is the content of documentFragment. + Node* parent = To(this); + + if (auto* template_element = DynamicTo(this)) { + parent = To(template_element->content()); + } + + if (parent->firstChild() == nullptr) + return s; + + auto* child = parent->firstChild(); + while (child != nullptr) { + if (auto* element = DynamicTo(child)) { + s += element->outerHTML(); + } else if (auto* text = DynamicTo(child)) { + s += text->data().ToStdString(); + } + child = child->nextSibling(); + } + + return s; +} + +void Element::setInnerHTML(const AtomicString& value, ExceptionState& exception_state) { + auto html = value.ToStdString(); + if (auto* template_element = DynamicTo(this)) { + HTMLParser::parseHTMLFragment(html.c_str(), html.size(), template_element->content()); + } else { + HTMLParser::parseHTMLFragment(html.c_str(), html.size(), this); + } +} + +void Element::_notifyNodeRemoved(Node* node) {} + +void Element::_notifyChildRemoved() {} + +void Element::_notifyNodeInsert(Node* insertNode){ + +}; + +void Element::_notifyChildInsert() {} + +void Element::_didModifyAttribute(const AtomicString& name, const AtomicString& oldId, const AtomicString& newId) {} + +void Element::_beforeUpdateId(JSValue oldIdValue, JSValue newIdValue) {} + +Node::NodeType Element::nodeType() const { + return kElementNode; +} + +bool Element::ChildTypeAllowed(NodeType type) const { + switch (type) { + case kElementNode: + case kTextNode: + case kCommentNode: + return true; + default: + break; + } + return false; +} + +} // namespace kraken diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts new file mode 100644 index 0000000000..898d455c76 --- /dev/null +++ b/bridge/core/dom/element.d.ts @@ -0,0 +1,49 @@ +import {Node} from "./node"; +import {Document} from "./document"; +import {ScrollToOptions} from "./scroll_to_options"; +import { ElementAttributes } from './legacy/element_attributes'; +import {CSSStyleDeclaration} from "../css/legacy/css_style_declaration"; + +interface Element extends Node { + readonly attributes: ElementAttributes; + readonly style: CSSStyleDeclaration; + + readonly clientHeight: number; + readonly clientLeft: number; + readonly clientTop: number; + readonly clientWidth: number; + readonly outerHTML: string; + innerHTML: string; + readonly ownerDocument: Document; + scrollLeft: number; + scrollTop: number; + /** + * Returns the HTML-uppercased qualified name. + */ + readonly tagName: string; + /** + * Returns element's first attribute whose qualified name is qualifiedName, and null if there is no such attribute otherwise. + */ + getAttribute(qualifiedName: string): string | null; + /** + * Sets the value of element's first attribute whose qualified name is qualifiedName to value. + */ + setAttribute(qualifiedName: string, value: string): void; + /** + * Removes element's first attribute whose qualified name is qualifiedName. + */ + removeAttribute(qualifiedName: string): void; + getBoundingClientRect(): BoundingClientRect; + + scroll(options?: ScrollToOptions): void; + scroll(x: number, y: number): void; + scrollBy(options?: ScrollToOptions): void; + scrollBy(x: number, y: number): void; + scrollTo(options?: ScrollToOptions): void; + scrollTo(x: number, y: number): void; + + // Kraken special API. + toBlob(devicePixelRatioValue?: double): Promise; + + new(): void; +} diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h new file mode 100644 index 0000000000..e060c4b9c0 --- /dev/null +++ b/bridge/core/dom/element.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_ELEMENT_H +#define KRAKENBRIDGE_ELEMENT_H + +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "container_node.h" +#include "core/css/legacy/css_style_declaration.h" +#include "legacy/bounding_client_rect.h" +#include "legacy/element_attributes.h" +#include "qjs_scroll_to_options.h" + +namespace kraken { + +class Element : public ContainerNode { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = Element*; + Element(const AtomicString& tag_name, Document* document, ConstructionType = kCreateElement); + + ElementAttributes* attributes() { return &EnsureElementAttributes(); } + ElementAttributes& EnsureElementAttributes(); + + bool hasAttribute(const AtomicString&, ExceptionState& exception_state); + AtomicString getAttribute(const AtomicString&, ExceptionState& exception_state); + + // Passing null as the second parameter removes the attribute when + // calling either of these set methods. + void setAttribute(const AtomicString&, const AtomicString& value); + void setAttribute(const AtomicString&, const AtomicString& value, ExceptionState&); + void removeAttribute(const AtomicString&, ExceptionState& exception_state); + BoundingClientRect* getBoundingClientRect(ExceptionState& exception_state); + void click(ExceptionState& exception_state); + void scroll(ExceptionState& exception_state); + void scroll(const std::shared_ptr& options, ExceptionState& exception_state); + void scroll(double x, double y, ExceptionState& exception_state); + void scrollTo(ExceptionState& exception_state); + void scrollTo(const std::shared_ptr& options, ExceptionState& exception_state); + void scrollTo(double x, double y, ExceptionState& exception_state); + void scrollBy(ExceptionState& exception_state); + void scrollBy(double x, double y, ExceptionState& exception_state); + void scrollBy(const std::shared_ptr& options, ExceptionState& exception_state); + + ScriptPromise toBlob(double device_pixel_ratio, ExceptionState& exception_state); + ScriptPromise toBlob(ExceptionState& exception_state); + + double clientHeight() const; + double clientWidth() const; + double clientLeft() const; + double clientTop() const; + + double scrollTop() const; + void setScrollTop(double v, ExceptionState& exception_state); + double scrollLeft() const; + void setScrollLeft(double v, ExceptionState& exception_state); + + std::string outerHTML(); + std::string innerHTML(); + void setInnerHTML(const AtomicString& value, ExceptionState& exception_state); + + bool HasTagName(const AtomicString&) const; + std::string nodeValue() const override; + AtomicString tagName() const { return tag_name_; } + std::string nodeName() const override; + + CSSStyleDeclaration* style(); + CSSStyleDeclaration& EnsureCSSStyleDeclaration(); + + Element& CloneWithChildren(CloneChildrenFlag flag, Document* = nullptr) const; + Element& CloneWithoutChildren(Document* = nullptr) const; + + NodeType nodeType() const override; + bool ChildTypeAllowed(NodeType) const override; + + // Clones attributes only. + void CloneAttributesFrom(const Element&); + bool HasEquivalentAttributes(const Element& other) const; + + // Step 5 of https://dom.spec.whatwg.org/#concept-node-clone + virtual void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) {} + + void Trace(GCVisitor* visitor) const override; + + protected: + private: + // Clone is private so that non-virtual CloneElementWithChildren and + // CloneElementWithoutChildren are used inst + Node* Clone(Document&, CloneChildrenFlag) const override; + virtual Element& CloneWithoutAttributesAndChildren(Document& factory) const; + + void _notifyNodeRemoved(Node* node); + void _notifyChildRemoved(); + void _notifyNodeInsert(Node* insertNode); + void _notifyChildInsert(); + void _didModifyAttribute(const AtomicString& name, const AtomicString& oldId, const AtomicString& newId); + void _beforeUpdateId(JSValue oldIdValue, JSValue newIdValue); + + Member attributes_; + Member cssom_wrapper_; + AtomicString tag_name_ = AtomicString::Empty(ctx()); +}; + +template +bool IsElementOfType(const Node&); +template <> +inline bool IsElementOfType(const Node& node) { + return node.IsElementNode(); +} +template +inline bool IsElementOfType(const Element& element) { + return IsElementOfType(static_cast(element)); +} +template <> +inline bool IsElementOfType(const Element&) { + return true; +} + +template <> +struct DowncastTraits { + static bool AllowFrom(const Node& node) { return node.IsElementNode(); } +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_ELEMENT_H diff --git a/bridge/bindings/qjs/dom/element_test.cc b/bridge/core/dom/element_test.cc similarity index 81% rename from bridge/bindings/qjs/dom/element_test.cc rename to bridge/core/dom/element_test.cc index 5a6040d6a2..ada3b1d324 100644 --- a/bridge/bindings/qjs/dom/element_test.cc +++ b/bridge/core/dom/element_test.cc @@ -2,10 +2,10 @@ * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ -#include "event_target.h" +#include "core/dom/legacy/bounding_client_rect.h" #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "page.h" +using namespace kraken; TEST(Element, setAttribute) { bool static errorCalled = false; @@ -18,7 +18,7 @@ TEST(Element, setAttribute) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "div.setAttribute('hello', 1234);" @@ -40,7 +40,7 @@ TEST(Element, getAttribute) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "let string = 'helloworld';" @@ -60,15 +60,20 @@ TEST(Element, getAttribute) { TEST(Element, setAttributeWithHTML) { bool static errorCalled = false; bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "100%"); + }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" - "div.innerHTML = '';"; + "div.innerHTML = '';" + "console.log(div.firstChild.style.width);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); } @@ -84,8 +89,8 @@ TEST(Element, style) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); - const char* code = "console.log('borderTop' in document.body.style, 'borderXXX' in document.body.style)"; + auto context = bridge->GetExecutingContext(); + const char* code = "console.log(Object.keys(document.body))"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); } @@ -101,7 +106,7 @@ TEST(Element, instanceofNode) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "console.log(div instanceof Node)"; @@ -122,7 +127,7 @@ TEST(Element, instanceofEventTarget) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "console.log(div instanceof EventTarget)"; @@ -133,26 +138,30 @@ TEST(Element, instanceofEventTarget) { } TEST(Element, stringifyBoundingClientRect) { - using namespace kraken::binding::qjs; + using namespace kraken; bool static errorCalled = false; bool static logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; - EXPECT_STREQ(message.c_str(), "{\"x\":10,\"y\":20,\"width\":30,\"height\":40,\"top\":10,\"right\":20,\"bottom\":30,\"left\":40}"); + EXPECT_STREQ(message.c_str(), + "{\"x\":10,\"y\":20,\"width\":30,\"height\":40,\"top\":10,\"right\":20,\"bottom\":30,\"left\":40}"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); NativeBoundingClientRect nativeRect{ 10.0, 20.0, 30.0, 40.0, 10.0, 20.0, 30.0, 40.0, }; - auto* clientRect = new BoundingClientRect(context, &nativeRect); - context->defineGlobalProperty("boundingClient", clientRect->jsObject); + { + MemberMutationScope scope{context}; + auto* clientRect = BoundingClientRect::Create(context, &nativeRect); + context->DefineGlobalProperty("boundingClient", clientRect->ToQuickJS()); + } const char* code = "console.log(JSON.stringify(boundingClient))"; bridge->evaluateScript(code, strlen(code), "vm://", 0); diff --git a/bridge/core/dom/element_traversal.h b/bridge/core/dom/element_traversal.h new file mode 100644 index 0000000000..774c7029ce --- /dev/null +++ b/bridge/core/dom/element_traversal.h @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ +#define KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ + +#include "element.h" +#include "foundation/macros.h" +#include "html_element_type_helper.h" +#include "node_traversal.h" +#include "traversal_range.h" + +namespace kraken { + +class HasTagName { + KRAKEN_STACK_ALLOCATED(); + + public: + explicit HasTagName(const AtomicString& tag_name) : tag_name_(tag_name) {} + bool operator()(const Element& element) const { return element.HasTagName(tag_name_); } + + private: + const AtomicString tag_name_; +}; + +// This class is used to traverse the DOM tree. It isn't meant to be +// constructed; instead, callers invoke the static methods, after templating it +// so that ElementType is the type of element they are interested in traversing. +// Traversals can also be predicated on a matcher, which will be used to +// filter the returned elements. A matcher is a callable - an object of a class +// that defines operator(). HasTagName above is an example of a matcher. +// +// For example, a caller could do this: +// Traversal::firstChild(some_node, +// HasTagName(html_names::kTitleTag)); +// +// This invocation would return the first child of |some_node| (which has to be +// a ContainerNode) for which HasTagName(html_names::kTitleTag) returned true, +// so it would return the first child of |someNode| which is a element. +// If the caller needs to traverse a Node this way, it's necessary to first +// check Node::IsContainerNode() and then use To<ContainerNode>(). Another way +// to achieve same behaviour is to use DynamicTo<ContainerNode>() which +// checks Node::IsContainerNode() and then returns container +// node. If the conditional check fails then it returns nullptr. +// DynamicTo<ContainerNode>() wraps IsContainerNode() so there is no need of +// an explicit conditional check. +// +// When looking for a specific element type, it is more efficient to do this: +// Traversal<HTMLTitleElement>::firstChild(someNode); +// +// Traversal can also be used to find ancestors and descendants; see the +// documentation in the class body below. +// +// Note that these functions do not traverse into child shadow trees of any +// shadow hosts they encounter. If you need to traverse the shadow DOM, you can +// manually traverse the shadow trees using a second Traversal, or use +// FlatTreeTraversal. +// +// ElementTraversal is a specialized version of Traversal<Element>. +template <class ElementType> +class Traversal { + KRAKEN_STATIC_ONLY(Traversal); + + public: + using TraversalNodeType = ElementType; + // First or last ElementType child of the node. + static ElementType* FirstChild(const ContainerNode& current) { return FirstChildTemplate(current); } + static ElementType* FirstChild(const Node& current) { return FirstChildTemplate(current); } + template <class MatchFunc> + static ElementType* FirstChild(const ContainerNode&, MatchFunc); + static ElementType* LastChild(const ContainerNode& current) { return LastChildTemplate(current); } + static ElementType* LastChild(const Node& current) { return LastChildTemplate(current); } + template <class MatchFunc> + static ElementType* LastChild(const ContainerNode&, MatchFunc); + + // First ElementType ancestor of the node. + static ElementType* FirstAncestor(const Node& current); + static ElementType* FirstAncestorOrSelf(Node& current) { return FirstAncestorOrSelfTemplate(current); } + static ElementType* FirstAncestorOrSelf(Element& current) { return FirstAncestorOrSelfTemplate(current); } + static const ElementType* FirstAncestorOrSelf(const Node& current) { + return FirstAncestorOrSelfTemplate(const_cast<Node&>(current)); + } + static const ElementType* FirstAncestorOrSelf(const Element& current) { + return FirstAncestorOrSelfTemplate(const_cast<Element&>(current)); + } + + // First or last ElementType descendant of the node. + // For pure Elements firstWithin() is always the same as firstChild(). + static ElementType* FirstWithin(const ContainerNode& current) { return FirstWithinTemplate(current); } + static ElementType* FirstWithin(const Node& current) { return FirstWithinTemplate(current); } + template <typename MatchFunc> + static ElementType* FirstWithin(const ContainerNode&, MatchFunc); + + static ElementType* InclusiveFirstWithin(Node& current) { + if (IsElementOfType<const ElementType>(current)) + return To<ElementType>(¤t); + return FirstWithin(current); + } + + static ElementType* LastWithin(const ContainerNode& current) { return LastWithinTemplate(current); } + static ElementType* LastWithin(const Node& current) { return LastWithinTemplate(current); } + template <class MatchFunc> + static ElementType* LastWithin(const ContainerNode&, MatchFunc); + static ElementType* LastWithinOrSelf(ElementType&); + + // Pre-order traversal skipping non-element nodes. + static ElementType* Next(const ContainerNode& current) { return NextTemplate(current); } + static ElementType* Next(const Node& current) { return NextTemplate(current); } + static ElementType* Next(const ContainerNode& current, const Node* stay_within) { + return NextTemplate(current, stay_within); + } + static ElementType* Next(const Node& current, const Node* stay_within) { return NextTemplate(current, stay_within); } + template <class MatchFunc> + static ElementType* Next(const ContainerNode& current, const Node* stay_within, MatchFunc); + static ElementType* Previous(const Node&); + static ElementType* Previous(const Node&, const Node* stay_within); + template <class MatchFunc> + static ElementType* Previous(const ContainerNode& current, const Node* stay_within, MatchFunc); + + // Like next, but skips children. + static ElementType* NextSkippingChildren(const Node&); + static ElementType* NextSkippingChildren(const Node&, const Node* stay_within); + // Previous / Next sibling. + static ElementType* PreviousSibling(const Node&); + template <class MatchFunc> + static ElementType* PreviousSibling(const Node&, MatchFunc); + static ElementType* NextSibling(const Node&); + template <class MatchFunc> + static ElementType* NextSibling(const Node&, MatchFunc); + + static TraversalSiblingRange<Traversal<ElementType>> ChildrenOf(const Node&); + static TraversalDescendantRange<Traversal<ElementType>> DescendantsOf(const Node&); + static TraversalInclusiveDescendantRange<Traversal<ElementType>> InclusiveDescendantsOf(const ElementType&); + static TraversalNextRange<Traversal<ElementType>> StartsAt(const ElementType&); + static TraversalNextRange<Traversal<ElementType>> StartsAfter(const Node&); + + private: + template <class NodeType> + static ElementType* FirstChildTemplate(NodeType&); + template <class NodeType> + static ElementType* LastChildTemplate(NodeType&); + template <class NodeType> + static ElementType* FirstAncestorOrSelfTemplate(NodeType&); + template <class NodeType> + static ElementType* FirstWithinTemplate(NodeType&); + template <class NodeType> + static ElementType* LastWithinTemplate(NodeType&); + template <class NodeType> + static ElementType* NextTemplate(NodeType&); + template <class NodeType> + static ElementType* NextTemplate(NodeType&, const Node* stay_within); +}; + +typedef Traversal<Element> ElementTraversal; + +template <class ElementType> +inline TraversalSiblingRange<Traversal<ElementType>> Traversal<ElementType>::ChildrenOf(const Node& start) { + return TraversalSiblingRange<Traversal<ElementType>>(Traversal<ElementType>::FirstChild(start)); +} + +template <class ElementType> +inline TraversalDescendantRange<Traversal<ElementType>> Traversal<ElementType>::DescendantsOf(const Node& root) { + return TraversalDescendantRange<Traversal<ElementType>>(&root); +} + +template <class ElementType> +inline TraversalInclusiveDescendantRange<Traversal<ElementType>> Traversal<ElementType>::InclusiveDescendantsOf( + const ElementType& root) { + return TraversalInclusiveDescendantRange<Traversal<ElementType>>(&root); +} + +template <class ElementType> +inline TraversalNextRange<Traversal<ElementType>> Traversal<ElementType>::StartsAt(const ElementType& start) { + return TraversalNextRange<Traversal<ElementType>>(&start); +} + +template <class ElementType> +inline TraversalNextRange<Traversal<ElementType>> Traversal<ElementType>::StartsAfter(const Node& start) { + return TraversalNextRange<Traversal<ElementType>>(Traversal<ElementType>::Next(start)); +} + +// Specialized for pure Element to exploit the fact that Elements parent is +// always either another Element or the root. +template <> +template <class NodeType> +inline Element* Traversal<Element>::FirstWithinTemplate(NodeType& current) { + return FirstChildTemplate(current); +} + +template <> +template <class NodeType> +inline Element* Traversal<Element>::NextTemplate(NodeType& current) { + Node* node = NodeTraversal::Next(current); + while (node && !node->IsElementNode()) + node = NodeTraversal::NextSkippingChildren(*node); + return To<Element>(node); +} + +template <> +template <class NodeType> +inline Element* Traversal<Element>::NextTemplate(NodeType& current, const Node* stay_within) { + Node* node = NodeTraversal::Next(current, stay_within); + while (node && !node->IsElementNode()) + node = NodeTraversal::NextSkippingChildren(*node, stay_within); + return To<Element>(node); +} + +// Generic versions. +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::FirstChildTemplate(NodeType& current) { + Node* node = current.firstChild(); + while (node && !IsElementOfType<const ElementType>(*node)) + node = node->nextSibling(); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::FirstChild(const ContainerNode& current, MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::FirstChild(current); + while (element && !is_match(*element)) + element = Traversal<ElementType>::NextSibling(*element); + return element; +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::FirstAncestor(const Node& current) { + ContainerNode* ancestor = current.parentNode(); + while (ancestor && !IsElementOfType<const ElementType>(*ancestor)) + ancestor = ancestor->parentNode(); + return To<ElementType>(ancestor); +} + +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::FirstAncestorOrSelfTemplate(NodeType& current) { + if (IsElementOfType<const ElementType>(current)) + return &To<ElementType>(current); + return FirstAncestor(current); +} + +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::LastChildTemplate(NodeType& current) { + Node* node = current.lastChild(); + while (node && !IsElementOfType<const ElementType>(*node)) + node = node->previousSibling(); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::LastChild(const ContainerNode& current, MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::LastChild(current); + while (element && !is_match(*element)) + element = Traversal<ElementType>::PreviousSibling(*element); + return element; +} + +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::FirstWithinTemplate(NodeType& current) { + Node* node = current.firstChild(); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::Next(*node, ¤t); + return To<ElementType>(node); +} + +template <class ElementType> +template <typename MatchFunc> +inline ElementType* Traversal<ElementType>::FirstWithin(const ContainerNode& current, MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::FirstWithin(current); + while (element && !is_match(*element)) + element = Traversal<ElementType>::Next(*element, ¤t, is_match); + return element; +} + +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::LastWithinTemplate(NodeType& current) { + Node* node = NodeTraversal::LastWithin(current); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::Previous(*node, ¤t); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::LastWithin(const ContainerNode& current, MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::LastWithin(current); + while (element && !is_match(*element)) + element = Traversal<ElementType>::Previous(*element, ¤t, is_match); + return element; +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::LastWithinOrSelf(ElementType& current) { + if (ElementType* last_descendant = LastWithin(current)) + return last_descendant; + return ¤t; +} + +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::NextTemplate(NodeType& current) { + Node* node = NodeTraversal::Next(current); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::Next(*node); + return To<ElementType>(node); +} + +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::NextTemplate(NodeType& current, const Node* stay_within) { + Node* node = NodeTraversal::Next(current, stay_within); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::Next(*node, stay_within); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::Next(const ContainerNode& current, + const Node* stay_within, + MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::Next(current, stay_within); + while (element && !is_match(*element)) + element = Traversal<ElementType>::Next(*element, stay_within); + return element; +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::Previous(const Node& current) { + Node* node = NodeTraversal::Previous(current); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::Previous(*node); + return To<ElementType>(node); +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::Previous(const Node& current, const Node* stay_within) { + Node* node = NodeTraversal::Previous(current, stay_within); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::Previous(*node, stay_within); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::Previous(const ContainerNode& current, + const Node* stay_within, + MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::Previous(current, stay_within); + while (element && !is_match(*element)) + element = Traversal<ElementType>::Previous(*element, stay_within); + return element; +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::NextSkippingChildren(const Node& current) { + Node* node = NodeTraversal::NextSkippingChildren(current); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::NextSkippingChildren(*node); + return To<ElementType>(node); +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::NextSkippingChildren(const Node& current, const Node* stay_within) { + Node* node = NodeTraversal::NextSkippingChildren(current, stay_within); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::NextSkippingChildren(*node, stay_within); + return To<ElementType>(node); +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::PreviousSibling(const Node& current) { + Node* node = current.previousSibling(); + while (node && !IsElementOfType<const ElementType>(*node)) + node = node->previousSibling(); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::PreviousSibling(const Node& current, MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::PreviousSibling(current); + while (element && !is_match(*element)) + element = Traversal<ElementType>::PreviousSibling(*element); + return element; +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::NextSibling(const Node& current) { + Node* node = current.nextSibling(); + while (node && !IsElementOfType<const ElementType>(*node)) + node = node->nextSibling(); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::NextSibling(const Node& current, MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::NextSibling(current); + while (element && !is_match(*element)) + element = Traversal<ElementType>::NextSibling(*element); + return element; +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ diff --git a/bridge/core/dom/empty_node_list.cc b/bridge/core/dom/empty_node_list.cc new file mode 100644 index 0000000000..09ea1983c6 --- /dev/null +++ b/bridge/core/dom/empty_node_list.cc @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "empty_node_list.h" +#include "core/dom/node.h" + +namespace kraken { + +EmptyNodeList::EmptyNodeList(Node* root_node) : owner_(root_node), NodeList(root_node->ctx()) {} + +void EmptyNodeList::Trace(GCVisitor* visitor) const {} + +Node* EmptyNodeList::VirtualOwnerNode() const { + return &OwnerNode(); +} + +} // namespace kraken diff --git a/bridge/core/dom/empty_node_list.h b/bridge/core/dom/empty_node_list.h new file mode 100644 index 0000000000..2e391cbf74 --- /dev/null +++ b/bridge/core/dom/empty_node_list.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ +#define KRAKENBRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ + +#include "node_list.h" + +namespace kraken { + +class ExceptionState; + +class EmptyNodeList : public NodeList { + public: + explicit EmptyNodeList(Node* root_node); + + Node& OwnerNode() const { return *owner_; } + void Trace(GCVisitor* visitor) const override; + + private: + unsigned length() const override { return 0; } + Node* item(unsigned, ExceptionState& exception_state) const override { return nullptr; } + + bool IsEmptyNodeList() const override { return true; } + Node* VirtualOwnerNode() const override; + + Node* owner_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ diff --git a/bridge/core/dom/events/add_event_listener_options.d.ts b/bridge/core/dom/events/add_event_listener_options.d.ts new file mode 100644 index 0000000000..ddb291508d --- /dev/null +++ b/bridge/core/dom/events/add_event_listener_options.d.ts @@ -0,0 +1,9 @@ +// @ts-ignore +import {EventListenerOptions} from "./event_listener_options"; + +// @ts-ignore +@Dictionary() +export interface AddEventListenerOptions extends EventListenerOptions { + passive: boolean; + once: boolean; +} diff --git a/bridge/core/dom/events/custom_event.cc b/bridge/core/dom/events/custom_event.cc new file mode 100644 index 0000000000..50ef78d3b0 --- /dev/null +++ b/bridge/core/dom/events/custom_event.cc @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "custom_event.h" +#include "bindings/qjs/native_value.h" +#include "bindings/qjs/qjs_engine_patch.h" + +#include <utility> + +namespace kraken { + +void bindCustomEvent(std::unique_ptr<ExecutionContext>& context) { + JSValue constructor = context->contextData()->constructorForType(&customEventTypeInfo); + JSValue prototype = context->contextData()->prototypeForType(&customEventTypeInfo); + + // Install methods on prototype. + INSTALL_FUNCTION(CustomEvent, prototype, initCustomEvent, 4); + + // Install readonly properties on prototype. + INSTALL_READONLY_PROPERTY(CustomEvent, prototype, detail); + + context->defineGlobalProperty("CustomEvent", constructor); +} + +JSClassID CustomEvent::classId{0}; + +CustomEvent* CustomEvent::create(JSContext* ctx, JSValue eventType, JSValue init) { + auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(ctx)); + JSValue prototype = context->contextData()->prototypeForType(&eventTypeInfo); + + auto* event = makeGarbageCollected<CustomEvent>(eventType, init)->initialize<CustomEvent>(ctx, &classId); + + if (!JS_IsNull(init)) { + JSAtom detailKey = JS_NewAtom(ctx, "detail"); + if (JS_HasProperty(ctx, init, detailKey)) { + JSValue detailValue = JS_GetProperty(ctx, init, detailKey); + event->m_detail = JS_DupValue(ctx, detailValue); + JS_FreeValue(ctx, detailValue); + } + JS_FreeAtom(ctx, detailKey); + } + + // Let instance inherit prototype methods. + JS_SetPrototype(ctx, event->toQuickJS(), prototype); + + return event; +} + +CustomEvent* CustomEvent::create(JSContext* ctx, NativeCustomEvent* nativeCustomEvent) { + auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(ctx)); + JSValue prototype = context->contextData()->prototypeForType(&eventTypeInfo); + + auto* event = makeGarbageCollected<CustomEvent>(nativeCustomEvent)->initialize<CustomEvent>(ctx, &classId); + + // Let instance inherit prototype methods. + JS_SetPrototype(ctx, event->toQuickJS(), prototype); + + return event; +} + +JSValue CustomEvent::constructor(ExecutionContext* context) { + return context->contextData()->constructorForType(&customEventTypeInfo); +} + +JSValue CustomEvent::prototype(ExecutionContext* context) { + return context->contextData()->prototypeForType(&customEventTypeInfo); +} + +CustomEvent::CustomEvent(JSValue eventType, JSValue eventInit) : Event(eventType, eventInit) { + if (!JS_IsNull(eventInit)) { + JSAtom detailKey = JS_NewAtom(m_ctx, "detail"); + if (JS_HasProperty(m_ctx, eventInit, detailKey)) { + JSValue detailValue = JS_GetProperty(m_ctx, eventInit, detailKey); + m_detail = JS_DupValue(m_ctx, detailValue); + JS_FreeValue(m_ctx, detailValue); + } + JS_FreeAtom(m_ctx, detailKey); + } +} + +CustomEvent::CustomEvent(NativeCustomEvent* nativeEvent) + : m_nativeCustomEvent(nativeEvent), Event(reinterpret_cast<NativeEvent*>(nativeEvent)) { + m_detail = JS_NewUnicodeString(m_runtime, m_ctx, nativeEvent->detail->string, nativeEvent->detail->length); +} + +void CustomEvent::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + Event::trace(rt, val, mark_func); + JS_MarkValue(rt, m_detail, mark_func); +} + +void CustomEvent::dispose() const { + // No needs to free m_nativeCustomEvent, Event::dispose() will handle this. + Event::dispose(); + JS_FreeValueRT(m_runtime, m_detail); +} + +IMPL_FUNCTION(CustomEvent, initCustomEvent)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + if (argc < 1) { + return JS_ThrowTypeError( + ctx, "Failed to execute 'initCustomEvent' on 'CustomEvent': 1 argument required, but only 0 present"); + } + + auto* eventInstance = static_cast<CustomEvent*>(JS_GetOpaque(this_val, CustomEvent::classId)); + if (eventInstance == nullptr) { + return JS_ThrowTypeError(ctx, "Failed to addEventListener: this is not an EventTarget object."); + } + + JSValue typeValue = argv[0]; + eventInstance->nativeEvent->type = jsValueToNativeString(ctx, typeValue).release(); + + if (argc <= 2) { + bool canBubble = JS_ToBool(ctx, argv[1]); + eventInstance->nativeEvent->bubbles = canBubble ? 1 : 0; + } + + if (argc <= 3) { + bool cancelable = JS_ToBool(ctx, argv[2]); + eventInstance->nativeEvent->cancelable = cancelable ? 1 : 0; + } + + if (argc <= 4) { + eventInstance->m_detail = JS_DupValue(ctx, argv[3]); + } + return JS_NULL; +} + +IMPL_PROPERTY_GETTER(CustomEvent, detail)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* customEventInstance = static_cast<CustomEvent*>(JS_GetOpaque(this_val, CustomEvent::classId)); + return JS_DupValue(ctx, customEventInstance->m_detail); +} + +} // namespace kraken diff --git a/bridge/core/dom/events/custom_event.h b/bridge/core/dom/events/custom_event.h new file mode 100644 index 0000000000..dc9597e586 --- /dev/null +++ b/bridge/core/dom/events/custom_event.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CUSTOM_EVENT_H +#define KRAKENBRIDGE_CUSTOM_EVENT_H + +#include "event.h" + +namespace kraken { + +void bindCustomEvent(ExecutionContext* context); + +struct NativeCustomEvent { + NativeEvent nativeEvent; + NativeString* detail{nullptr}; +}; + +class CustomEvent : public Event { + public: + static JSClassID classId; + static CustomEvent* create(JSContext* ctx, JSValue eventType, JSValue init); + static CustomEvent* create(JSContext* ctx, NativeCustomEvent* nativeCustomEvent); + static JSValue constructor(ExecutionContext* context); + static JSValue prototype(ExecutionContext* context); + + CustomEvent(JSValue eventType, JSValue init); + CustomEvent(NativeCustomEvent* nativeEvent); + + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void dispose() const override; + + DEFINE_FUNCTION(initCustomEvent); + + DEFINE_PROTOTYPE_READONLY_PROPERTY(detail); + + private: + NativeCustomEvent* m_nativeCustomEvent{nullptr}; + JSValue m_detail{JS_NULL}; +}; + +// class CustomEventInstance : public EventInstance { +// public: +// explicit CustomEventInstance(CustomEvent* jsCustomEvent, JSAtom CustomEventType, JSValue eventInit); +// explicit CustomEventInstance(CustomEvent* jsCustomEvent, NativeCustomEvent* nativeCustomEvent); +// +// private: + +// NativeCustomEvent* nativeCustomEvent{nullptr}; +// friend CustomEvent; +//}; + +auto customEventCreator = + [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) + -> JSValue { + if (argc < 1) { + return JS_ThrowTypeError(ctx, "Failed to construct 'CustomEvent': 1 argument required, but only 0 present."); + } + + JSValue typeValue = argv[0]; + JSValue customEventInit = JS_NULL; + + if (argc == 2) { + customEventInit = argv[1]; + } + + auto* customEvent = CustomEvent::create(ctx, typeValue, customEventInit); + // auto* customEvent = new CustomEventInstance(CustomEvent::instance(context()), typeAtom, customEventInit); + // JS_FreeAtom(m_ctx, typeAtom); + return customEvent->toQuickJS(); +}; + +const WrapperTypeInfo customEventTypeInfo = {"CustomEvent", &eventTypeInfo, customEventCreator}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CUSTOM_EVENT_H diff --git a/bridge/bindings/qjs/dom/custom_event_test.cc b/bridge/core/dom/events/custom_event_test.cc similarity index 100% rename from bridge/bindings/qjs/dom/custom_event_test.cc rename to bridge/core/dom/events/custom_event_test.cc diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc new file mode 100644 index 0000000000..5c569b6795 --- /dev/null +++ b/bridge/core/dom/events/event.cc @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "event.h" +#include "core/executing_context.h" +#include "event_target.h" + +namespace kraken { + +Event* Event::From(ExecutingContext* context, NativeEvent* native_event) { + AtomicString event_type = AtomicString::From(context->ctx(), native_event->type); + + auto* event = + MakeGarbageCollected<Event>(context, event_type, native_event->bubbles == 0 ? Bubbles::kNo : Bubbles::kYes, + native_event->cancelable == 0 ? Cancelable::kNo : Cancelable::kYes, + ComposedMode::kComposed, native_event->timeStamp); + event->SetTarget(static_cast<EventTarget*>(native_event->target)); + event->SetCurrentTarget(static_cast<EventTarget*>(native_event->currentTarget)); + event->default_prevented_ = native_event->defaultPrevented; + return event; +} + +Event::Event(ExecutingContext* context) : Event(context, AtomicString::Empty(context->ctx())) {} + +Event::Event(ExecutingContext* context, const AtomicString& event_type) + : Event(context, + event_type, + Bubbles::kNo, + Cancelable::kNo, + ComposedMode::kComposed, + std::chrono::system_clock::now().time_since_epoch().count()) {} + +Event::Event(ExecutingContext* context, const AtomicString& type, const std::shared_ptr<EventInit>& init) + : ScriptWrappable(context->ctx()), + type_(type), + bubbles_(init->bubbles()), + cancelable_(init->cancelable()), + composed_(init->composed()) {} + +Event::Event(ExecutingContext* context, + const AtomicString& event_type, + Bubbles bubbles, + Cancelable cancelable, + ComposedMode composed_mode, + double time_stamp) + : ScriptWrappable(context->ctx()), + type_(event_type), + bubbles_(bubbles == Bubbles::kYes), + cancelable_(cancelable == Cancelable::kYes), + composed_(composed_mode == ComposedMode::kComposed), + propagation_stopped_(false), + immediate_propagation_stopped_(false), + default_prevented_(false), + default_handled_(false), + was_initialized_(true), + is_trusted_(false), + handling_passive_(PassiveMode::kNotPassiveDefault), + prevent_default_called_on_uncancelable_event_(false), + fire_only_capture_listeners_at_target_(false), + fire_only_non_capture_listeners_at_target_(false), + event_phase_(0), + current_target_(nullptr), + time_stamp_(time_stamp) {} + +void Event::SetType(const AtomicString& type) { + type_ = type; +} + +EventTarget* Event::target() const { + return target_; +} + +void Event::SetTarget(EventTarget* target) { + target_ = target; +} + +EventTarget* Event::currentTarget() const { + return current_target_; +} + +EventTarget* Event::srcElement() const { + return target(); +} + +void Event::SetCurrentTarget(EventTarget* target) { + current_target_ = target; +} + +bool Event::IsUIEvent() const { + return false; +} + +bool Event::IsMouseEvent() const { + return false; +} + +bool Event::IsFocusEvent() const { + return false; +} + +bool Event::IsKeyboardEvent() const { + return false; +} + +bool Event::IsTouchEvent() const { + return false; +} + +bool Event::IsGestureEvent() const { + return false; +} + +bool Event::IsPointerEvent() const { + return false; +} + +bool Event::IsInputEvent() const { + return false; +} + +bool Event::IsDragEvent() const { + return false; +} + +bool Event::IsBeforeUnloadEvent() const { + return false; +} + +bool Event::IsErrorEvent() const { + return false; +} + +void Event::preventDefault(ExceptionState& exception_state) { + if (handling_passive_ != PassiveMode::kNotPassive && handling_passive_ != PassiveMode::kNotPassiveDefault) { + return; + } + + default_prevented_ = true; +} + +void Event::initEvent(const AtomicString& event_type, bool bubbles, bool cancelable, ExceptionState& exception_state) { + if (IsBeingDispatched()) { + return; + } + + was_initialized_ = true; + propagation_stopped_ = false; + immediate_propagation_stopped_ = false; + default_prevented_ = false; + + type_ = event_type; + bubbles_ = bubbles; + cancelable_ = cancelable; +} + +void Event::SetHandlingPassive(PassiveMode mode) { + handling_passive_ = mode; +} + +void Event::Trace(GCVisitor* visitor) const {} + +} // namespace kraken diff --git a/bridge/core/dom/events/event.d.ts b/bridge/core/dom/events/event.d.ts new file mode 100644 index 0000000000..0a1eb5a935 --- /dev/null +++ b/bridge/core/dom/events/event.d.ts @@ -0,0 +1,49 @@ +import { EventTarget } from './event_target'; +import { EventInit } from './event_init'; + +interface Event { + /**s + * Returns true or false depending on how event was initialized. True if event goes through its target's ancestors in reverse tree order, and false otherwise. + */ + readonly bubbles: boolean; + cancelBubble: boolean; + /** + * Returns true or false depending on how event was initialized. Its return value does not always carry meaning, but true can indicate that part of the operation during which event was dispatched, can be canceled by invoking the preventDefault() method. + */ + readonly cancelable: boolean; + /** + * Returns the object whose event listener's callback is currently being invoked. + */ + readonly currentTarget: EventTarget | null; + /** + * Returns true if preventDefault() was invoked successfully to indicate cancelation, and false otherwise. + */ + readonly defaultPrevented: boolean; + readonly srcElement: EventTarget | null; + readonly target: EventTarget | null; + readonly isTrusted: boolean; + /** + * Returns the event's timestamp as the number of milliseconds measured relative to the time origin. + */ + readonly timeStamp: number; + /** + * Returns the type of event, e.g. "click", "hashchange", or "submit". + */ + readonly type: string; + /** @deprecated */ + initEvent(type: string, bubbles: boolean, cancelable: boolean): void; + /** + * If invoked when the cancelable attribute value is true, and while executing a listener for the event with passive set to false, signals to the operation that caused event to be dispatched that it needs to be canceled. + */ + preventDefault(): void; + /** + * Invoking this method prevents event from reaching any registered event listeners after the current one finishes running and, when dispatched in a tree, also prevents event from reaching any other objects. + */ + stopImmediatePropagation(): void; + /** + * When dispatched in a tree, invoking this method prevents event from reaching any objects other than the current object. + */ + stopPropagation(): void; + + new(type: string, options?: EventInit) : Event; +} diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h new file mode 100644 index 0000000000..88f4783549 --- /dev/null +++ b/bridge/core/dom/events/event.h @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_EVENT_H +#define KRAKENBRIDGE_EVENT_H + +#include <cinttypes> +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/script_wrappable.h" +#include "core/executing_context.h" +#include "foundation/native_string.h" +#include "qjs_event_init.h" + +namespace kraken { + +class EventTarget; +class ExceptionState; + +// Dart generated nativeEvent member are force align to 64-bit system. So all members in NativeEvent should have 64 bit +// width. +#if ANDROID_32_BIT +struct NativeEvent { + int64_t type{0}; + int64_t bubbles{0}; + int64_t cancelable{0}; + int64_t timeStamp{0}; + int64_t defaultPrevented{0}; + // The pointer address of target EventTargetInstance object. + int64_t target{0}; + // The pointer address of current target EventTargetInstance object. + int64_t currentTarget{0}; +}; +#else +// Use pointer instead of int64_t on 64 bit system can help compiler to choose best register for better running +// performance. +struct NativeEvent { + NativeString* type{nullptr}; + int64_t bubbles{0}; + int64_t cancelable{0}; + int64_t timeStamp{0}; + int64_t defaultPrevented{0}; + // The pointer address of target EventTargetInstance object. + void* target{nullptr}; + // The pointer address of current target EventTargetInstance object. + void* currentTarget{nullptr}; +}; +#endif + +struct RawEvent { + uint64_t* bytes; + int64_t length; +}; + +class Event : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = Event*; + + enum class Bubbles { + kNo, + kYes, + }; + + enum class Cancelable { + kNo, + kYes, + }; + + enum class ComposedMode { + kComposed, + kScoped, + }; + + enum class PassiveMode { + // Not passive, default initialized. + kNotPassiveDefault, + // Not passive, explicitly specified. + kNotPassive, + // Passive, explicitly specified. + kPassive, + // Passive, not explicitly specified and forced due to document level + // listener. + kPassiveForcedDocumentLevel, + // Passive, default initialized. + kPassiveDefault, + }; + + enum PhaseType { kNone = 0, kCapturingPhase = 1, kAtTarget = 2, kBubblingPhase = 3 }; + + static Event* Create(ExecutingContext* context) { return MakeGarbageCollected<Event>(context); }; + static Event* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { + return MakeGarbageCollected<Event>(context, type); + }; + static Event* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<EventInit>& init, + ExceptionState& exception_state) { + return MakeGarbageCollected<Event>(context, type, init); + }; + + static Event* From(ExecutingContext* context, NativeEvent* native_event); + + Event() = delete; + explicit Event(ExecutingContext* context); + explicit Event(ExecutingContext* context, const AtomicString& event_type); + explicit Event(ExecutingContext* context, const AtomicString& type, const std::shared_ptr<EventInit>& init); + explicit Event(ExecutingContext* context, + const AtomicString& event_type, + Bubbles bubbles, + Cancelable cancelable, + ComposedMode composed_mode, + double timeStamp); + + bool propagationStopped() const { return propagation_stopped_; } + bool bubbles() { return bubbles_; }; + double timeStamp() { return time_stamp_; } + bool propagationImmediatelyStopped(ExceptionState& exception_state) { return immediate_propagation_stopped_; } + bool cancelable() const { return cancelable_; } + const AtomicString& type() { return type_; }; + void SetType(const AtomicString& type); + EventTarget* target() const; + void SetTarget(EventTarget* target); + EventTarget* currentTarget() const; + void SetCurrentTarget(EventTarget* target); + + uint8_t eventPhase() const { return event_phase_; } + void SetEventPhase(uint8_t event_phase) { event_phase_ = event_phase; } + + // These events are general classes of events. + virtual bool IsUIEvent() const; + virtual bool IsMouseEvent() const; + virtual bool IsFocusEvent() const; + virtual bool IsKeyboardEvent() const; + virtual bool IsTouchEvent() const; + virtual bool IsGestureEvent() const; + virtual bool IsPointerEvent() const; + virtual bool IsInputEvent() const; + + // Drag events are a subset of mouse events. + virtual bool IsDragEvent() const; + + virtual bool IsBeforeUnloadEvent() const; + virtual bool IsErrorEvent() const; + + // This callback is invoked when an event listener has been dispatched + // at the current target. It should only be used to influence UMA metrics + // and not change functionality since observing the presence of listeners + // is dangerous. + virtual void DoneDispatchingEventAtCurrentTarget() {} + + bool cancelBubble() const { return propagationStopped(); } + void setCancelBubble(bool cancel, ExceptionState& exception_state) { + if (cancel) { + propagation_stopped_ = true; + } + }; + + bool IsBeingDispatched() { return eventPhase(); } + + // IE legacy + EventTarget* srcElement() const; + + void stopPropagation(ExceptionState& exception_state) { propagation_stopped_ = true; } + void SetStopPropagation(bool stop_propagation) { propagation_stopped_ = stop_propagation; } + void stopImmediatePropagation(ExceptionState& exception_state) { immediate_propagation_stopped_ = true; } + void SetStopImmediatePropagation(bool stop_immediate_propagation) { + immediate_propagation_stopped_ = stop_immediate_propagation; + } + void initEvent(const AtomicString& event_type, bool bubbles, bool cancelable, ExceptionState& exception_state); + + bool ImmediatePropagationStopped() const { return immediate_propagation_stopped_; } + bool WasInitialized() { return was_initialized_; } + + void SetHandlingPassive(PassiveMode); + + bool isTrusted() const { return is_trusted_; } + void SetTrusted(bool value) { is_trusted_ = value; } + + bool defaultPrevented() const { return default_prevented_; } + void preventDefault(ExceptionState& exception_state); + + bool DefaultHandled() const { return default_handled_; } + void SetDefaultHandled() { default_handled_ = true; } + + void SetFireOnlyCaptureListenersAtTarget(bool fire_only_capture_listeners_at_target) { + assert(event_phase_ == kAtTarget); + fire_only_capture_listeners_at_target_ = fire_only_capture_listeners_at_target; + } + + void SetFireOnlyNonCaptureListenersAtTarget(bool fire_only_non_capture_listeners_at_target) { + assert(event_phase_ = kAtTarget); + fire_only_non_capture_listeners_at_target_ = fire_only_non_capture_listeners_at_target; + } + + bool FireOnlyCaptureListenersAtTarget() const { return fire_only_capture_listeners_at_target_; } + bool FireOnlyNonCaptureListenersAtTarget() const { return fire_only_non_capture_listeners_at_target_; } + + void Trace(GCVisitor* visitor) const override; + + protected: + PassiveMode HandlingPassive() const { return handling_passive_; } + + AtomicString type_; + + unsigned bubbles_ : 1; + unsigned cancelable_ : 1; + unsigned composed_ : 1; + double time_stamp_{0.0}; + + unsigned propagation_stopped_ : 1; + unsigned immediate_propagation_stopped_ : 1; + unsigned default_prevented_ : 1; + unsigned default_handled_ : 1; + unsigned was_initialized_ : 1; + unsigned is_trusted_ : 1; + + PassiveMode handling_passive_; + uint8_t event_phase_ = PhaseType::kNone; + + // Whether preventDefault was called on uncancelable event. + unsigned prevent_default_called_on_uncancelable_event_ : 1; + + unsigned fire_only_capture_listeners_at_target_ : 1; + unsigned fire_only_non_capture_listeners_at_target_ : 1; + + EventTarget* target_{nullptr}; + EventTarget* current_target_{nullptr}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_EVENT_H diff --git a/bridge/core/dom/events/event_init.d.ts b/bridge/core/dom/events/event_init.d.ts new file mode 100644 index 0000000000..79988766ff --- /dev/null +++ b/bridge/core/dom/events/event_init.d.ts @@ -0,0 +1,8 @@ + +// @ts-ignore +@Dictionary() +export interface EventInit { + bubbles?: boolean; + cancelable?: boolean; + composed?: boolean; +} diff --git a/bridge/core/dom/events/event_listener.h b/bridge/core/dom/events/event_listener.h new file mode 100644 index 0000000000..c131c041e9 --- /dev/null +++ b/bridge/core/dom/events/event_listener.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ +#define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ + +#include "core/executing_context.h" +#include "event.h" + +namespace kraken { + +class JSBasedEventListener; + +// EventListener represents 'callback' in 'event listener' in DOM standard. +// https://dom.spec.whatwg.org/#concept-event-listener +// +// While RegisteredEventListener represents 'event listener', which consists of +// - type +// - callback +// - capture +// - passive +// - once +// - removed +// EventListener represents 'callback' part. +class EventListener { + public: + EventListener(const EventListener&) = delete; + EventListener& operator=(const EventListener&) = delete; + ~EventListener() = default; + + // Invokes this event listener. + virtual void Invoke(ExecutingContext* context, Event*, ExceptionState& exception_state) = 0; + + virtual bool IsJSBasedEventListener() const { return false; } + + // Returns true if this implements IDL EventHandler family. + virtual bool IsEventHandler() const { return false; } + + // Returns true if this implements IDL EventHandler family and the value is + // a content attribute (or compiled from a content attribute). + virtual bool IsEventHandlerForContentAttribute() const { return false; } + + // Returns true if this event listener is considered as the same with the + // other event listener (in context of EventTarget.removeEventListener). + // See also |RegisteredEventListener::Matches|. + // + // This function must satisfy the symmetric property; a.Matches(b) must + // produce the same result as b.Matches(a). + virtual bool Matches(const EventListener&) const = 0; + + virtual void Trace(GCVisitor* visitor) const = 0; + + private: + EventListener() = default; + + friend JSBasedEventListener; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc new file mode 100644 index 0000000000..ad65dd90fd --- /dev/null +++ b/bridge/core/dom/events/event_listener_map.cc @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "event_listener_map.h" + +namespace kraken { + +EventListenerMap::EventListenerMap() {} + +static bool AddListenerToVector(EventListenerVector* vector, + const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<AddEventListenerOptions>& options, + RegisteredEventListener* registered_event_listener) { + *registered_event_listener = RegisteredEventListener(listener, options); + + if (std::find(vector->begin(), vector->end(), *registered_event_listener) != vector->end()) { + return false; // Duplicate listener. + } + + vector->push_back(*registered_event_listener); + return true; +} + +static bool RemoveListenerFromVector(EventListenerVector* listener_vector, + const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<EventListenerOptions>& options, + size_t* index_of_removed_listener, + RegisteredEventListener* registered_event_listener) { + // Do a manual search for the matching listener. It is not + // possible to create a listener on the stack because of the + // const on |listener|. + auto it = std::find_if(listener_vector->begin(), listener_vector->end(), + [listener, options](const RegisteredEventListener& event_listener) -> bool { + return event_listener.Matches(listener, options); + }); + + if (it == listener_vector->end()) { + *index_of_removed_listener = -1; + return false; + } + + *registered_event_listener = *it; + *index_of_removed_listener = it - listener_vector->begin(); + listener_vector->erase(it); + + return true; +} + +bool EventListenerMap::Contains(const AtomicString& event_type) const { + for (const auto& entry : entries_) { + if (entry.first == event_type) + return true; + } + return false; +} + +bool EventListenerMap::ContainsCapturing(const AtomicString& event_type) const { + for (const auto& entry : entries_) { + if (entry.first == event_type) { + for (const auto& event_listener : *entry.second) { + if (event_listener.Capture()) + return true; + } + return false; + } + } + return false; +} + +void EventListenerMap::Clear() { + entries_.clear(); +} + +bool EventListenerMap::Add(const AtomicString& event_type, + const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<AddEventListenerOptions>& options, + RegisteredEventListener* registered_event_listener) { + for (const auto& entry : entries_) { + if (entry.first == event_type) + return AddListenerToVector(entry.second.get(), listener, options, registered_event_listener); + } + + entries_.emplace_back(event_type, std::make_unique<EventListenerVector>()); + return AddListenerToVector(entries_.back().second.get(), listener, options, registered_event_listener); +} + +bool EventListenerMap::Remove(const AtomicString& event_type, + const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<EventListenerOptions>& options, + size_t* index_of_removed_listener, + RegisteredEventListener* registered_event_listener) { + for (unsigned i = 0; i < entries_.size(); ++i) { + if (entries_[i].first == event_type) { + bool was_removed = RemoveListenerFromVector(entries_[i].second.get(), listener, options, + index_of_removed_listener, registered_event_listener); + if (entries_[i].second->empty()) { + entries_.erase(entries_.begin() + i); + } + return was_removed; + } + } + + return false; +} + +EventListenerVector* EventListenerMap::Find(const AtomicString& event_type) { + for (const auto& entry : entries_) { + if (entry.first == event_type) + return entry.second.get(); + } + + return nullptr; +} + +void EventListenerMap::Trace(GCVisitor* visitor) const { + for (const auto& entry : entries_) { + for (auto& listener : *entry.second) { + listener.Trace(visitor); + } + } +} + +} // namespace kraken diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h new file mode 100644 index 0000000000..d8b3f8486e --- /dev/null +++ b/bridge/core/dom/events/event_listener_map.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ + +#include <quickjs/quickjs.h> + +#include <vector> + +#include "bindings/qjs/atomic_string.h" +#include "event_listener.h" +#include "foundation/macros.h" +#include "registered_eventListener.h" + +namespace kraken { + +class AddEventListenerOptions; +class EventListenerOptions; + +using EventListenerVector = std::vector<RegisteredEventListener>; + +class EventListenerMap final { + KRAKEN_DISALLOW_NEW(); + + public: + EventListenerMap(); + EventListenerMap(const EventListenerMap&) = delete; + EventListenerMap& operator=(const EventListenerMap&) = delete; + + bool IsEmpty() const { return entries_.empty(); } + bool Contains(const AtomicString& event_type) const; + bool ContainsCapturing(const AtomicString& event_type) const; + void Clear(); + bool Add(const AtomicString& event_type, + const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<AddEventListenerOptions>& options, + RegisteredEventListener* registered_event_listener); + bool Remove(const AtomicString& event_type, + const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<EventListenerOptions>& options, + size_t* index_of_removed_listener, + RegisteredEventListener* registered_event_listener); + EventListenerVector* Find(const AtomicString& event_type); + + void Trace(GCVisitor* visitor) const; + + private: + // EventListener handlers registered with addEventListener API. + // We use vector instead of hashMap because + // - vector is much more space efficient than hashMap. + // - An EventTarget rarely has event listeners for many event types, and + // vector is faster in such cases. + std::vector<std::pair<AtomicString, std::unique_ptr<EventListenerVector>>> entries_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ diff --git a/bridge/core/dom/events/event_listener_options.d.ts b/bridge/core/dom/events/event_listener_options.d.ts new file mode 100644 index 0000000000..947754033b --- /dev/null +++ b/bridge/core/dom/events/event_listener_options.d.ts @@ -0,0 +1,5 @@ +// @ts-ignore +@Dictionary() +export interface EventListenerOptions { + capture: boolean; +} diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc new file mode 100644 index 0000000000..e363ef4b59 --- /dev/null +++ b/bridge/core/dom/events/event_target.cc @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "event_target.h" +#include "bindings/qjs/converter_impl.h" +#include "event_type_names.h" +#include "qjs_add_event_listener_options.h" + +#define PROPAGATION_STOPPED 1 +#define PROPAGATION_CONTINUE 0 + +#if UNIT_TEST +#include "kraken_test_env.h" +#endif + +namespace kraken { + +static std::atomic<int32_t> global_event_target_id{0}; + +Event::PassiveMode EventPassiveMode(const RegisteredEventListener& event_listener) { + if (!event_listener.Passive()) { + return Event::PassiveMode::kNotPassiveDefault; + } + return Event::PassiveMode::kPassiveDefault; +} + +// EventTargetData +EventTargetData::EventTargetData() {} + +EventTargetData::~EventTargetData() {} + +void EventTargetData::Trace(GCVisitor* visitor) const { + event_listener_map.Trace(visitor); +} + +EventTarget* EventTarget::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected<EventTargetWithInlineData>(context); +} + +EventTarget::~EventTarget() { + // GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, nullptr); +} + +EventTarget::EventTarget(ExecutingContext* context) + : BindingObject(context), ScriptWrappable(context->ctx()), event_target_id_(global_event_target_id++) {} + +bool EventTarget::addEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& event_listener, + const std::shared_ptr<AddEventListenerOptions>& options, + ExceptionState& exception_state) { + return AddEventListenerInternal(event_type, event_listener, options); +} + +bool EventTarget::addEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& event_listener, + ExceptionState& exception_state) { + std::shared_ptr<AddEventListenerOptions> options = AddEventListenerOptions::Create(); + return AddEventListenerInternal(event_type, event_listener, options); +} + +bool EventTarget::removeEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& event_listener, + ExceptionState& exception_state) { + std::shared_ptr<EventListenerOptions> options = EventListenerOptions::Create(); + return RemoveEventListenerInternal(event_type, event_listener, options); +} + +bool EventTarget::removeEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& event_listener, + const std::shared_ptr<EventListenerOptions>& options, + ExceptionState& exception_state) { + return RemoveEventListenerInternal(event_type, event_listener, options); +} + +bool EventTarget::removeEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& event_listener, + bool use_capture, + ExceptionState& exception_state) { + auto options = EventListenerOptions::Create(); + options->setCapture(use_capture); + return RemoveEventListenerInternal(event_type, event_listener, options); +} + +bool EventTarget::dispatchEvent(Event* event, ExceptionState& exception_state) { + if (!event->WasInitialized()) { + exception_state.ThrowException(event->ctx(), ErrorType::InternalError, "The event provided is uninitialized."); + return false; + } + + if (event->IsBeingDispatched()) { + exception_state.ThrowException(event->ctx(), ErrorType::InternalError, "The event is already being dispatched."); + return false; + } + + if (!GetExecutingContext()) + return false; + + event->SetTrusted(false); + + // Return whether the event was cancelled or not to JS not that it + // might have actually been default handled; so check only against + // CanceledByEventHandler. + return DispatchEventInternal(*event, exception_state) != DispatchEventResult::kCanceledByEventHandler; +} + +DispatchEventResult EventTarget::FireEventListeners(Event& event, ExceptionState& exception_state) { + assert(event.WasInitialized()); + + EventTargetData* d = GetEventTargetData(); + if (!d) + return DispatchEventResult::kNotCanceled; + + EventListenerVector* listeners_vector = d->event_listener_map.Find(event.type()); + + bool fired_event_listeners = false; + if (listeners_vector) { + fired_event_listeners = FireEventListeners(event, d, *listeners_vector, exception_state); + } + + // Only invoke the callback if event listeners were fired for this phase. + if (fired_event_listeners) { + event.DoneDispatchingEventAtCurrentTarget(); + } + return GetDispatchEventResult(event); +} + +DispatchEventResult EventTarget::GetDispatchEventResult(const Event& event) { + if (event.defaultPrevented()) + return DispatchEventResult::kCanceledByEventHandler; + if (event.DefaultHandled()) + return DispatchEventResult::kCanceledByDefaultEventHandler; + return DispatchEventResult::kNotCanceled; +} + +bool EventTarget::AddEventListenerInternal(const AtomicString& event_type, + const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<AddEventListenerOptions>& options) { + if (!listener) + return false; + + RegisteredEventListener registered_listener; + bool added = EnsureEventTargetData().event_listener_map.Add(event_type, listener, options, ®istered_listener); + + return added; +} + +bool EventTarget::RemoveEventListenerInternal(const AtomicString& event_type, + const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<EventListenerOptions>& options) { + if (!listener) + return false; + + EventTargetData* d = GetEventTargetData(); + if (!d) + return false; + + size_t index_of_removed_listener; + RegisteredEventListener registered_listener; + + if (!d->event_listener_map.Remove(event_type, listener, options, &index_of_removed_listener, ®istered_listener)) + return false; + + // Notify firing events planning to invoke the listener at 'index' that + // they have one less listener to invoke. + if (d->firing_event_iterators) { + for (const auto& firing_iterator : *d->firing_event_iterators) { + if (event_type != firing_iterator.event_type) + continue; + + if (index_of_removed_listener >= firing_iterator.end) + continue; + + --firing_iterator.end; + // Note that when firing an event listener, + // firingIterator.iterator indicates the next event listener + // that would fire, not the currently firing event + // listener. See EventTarget::fireEventListeners. + if (index_of_removed_listener < firing_iterator.iterator) + --firing_iterator.iterator; + } + } + + return true; +} + +DispatchEventResult EventTarget::DispatchEventInternal(Event& event, ExceptionState& exception_state) { + event.SetTarget(this); + event.SetCurrentTarget(this); + event.SetEventPhase(Event::kAtTarget); + DispatchEventResult dispatch_result = FireEventListeners(event, exception_state); + event.SetEventPhase(0); + return dispatch_result; +} + +NativeValue EventTarget::HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const { + return Native_NewNull(); +} + +bool EventTarget::FireEventListeners(Event& event, + EventTargetData* d, + EventListenerVector& entry, + ExceptionState& exception_state) { + // Fire all listeners registered for this event. Don't fire listeners removed + // during event dispatch. Also, don't fire event listeners added during event + // dispatch. Conveniently, all new event listeners will be added after or at + // index |size|, so iterating up to (but not including) |size| naturally + // excludes new event listeners. + ExecutingContext* context = GetExecutingContext(); + if (!context) + return false; + + size_t i = 0; + size_t size = entry.size(); + if (!d->firing_event_iterators) + d->firing_event_iterators = std::make_unique<FiringEventIteratorVector>(); + d->firing_event_iterators->push_back(FiringEventIterator(event.type(), i, size)); + + bool fired_listener = false; + + while (i < size) { + // If stopImmediatePropagation has been called, we just break out + // immediately, without handling any more events on this target. + if (event.ImmediatePropagationStopped()) + break; + + RegisteredEventListener registered_listener = entry[i]; + + // Move the iterator past this event listener. This must match + // the handling of the FiringEventIterator::iterator in + // EventTarget::removeEventListener. + ++i; + + if (!registered_listener.ShouldFire(event)) + continue; + + std::shared_ptr<EventListener> listener = registered_listener.Callback(); + // The listener will be retained by Member<EventListener> in the + // registeredListener, i and size are updated with the firing event iterator + // in case the listener is removed from the listener vector below. + if (registered_listener.Once()) + removeEventListener(event.type(), listener, registered_listener.Capture(), exception_state); + + event.SetHandlingPassive(EventPassiveMode(registered_listener)); + + // To match Mozilla, the AT_TARGET phase fires both capturing and bubbling + // event listeners, even though that violates some versions of the DOM spec. + listener->Invoke(context, &event, exception_state); + fired_listener = true; + + event.SetHandlingPassive(Event::PassiveMode::kNotPassive); + + assert(i <= size); + } + d->firing_event_iterators->pop_back(); + return fired_listener; +} + +void EventTargetWithInlineData::Trace(GCVisitor* visitor) const { + data_.Trace(visitor); +} + +} // namespace kraken + +// namespace kraken::binding::qjs diff --git a/bridge/core/dom/events/event_target.d.ts b/bridge/core/dom/events/event_target.d.ts new file mode 100644 index 0000000000..63d0599ee0 --- /dev/null +++ b/bridge/core/dom/events/event_target.d.ts @@ -0,0 +1,12 @@ + +// TODO: support options for addEventListener and removeEventListener +import {AddEventListenerOptions} from "./add_event_listener_options"; +import {EventListenerOptions} from "./event_listener_options"; +import {Event} from "./event"; + +interface EventTarget { + addEventListener(type: string, callback: JSEventListener | null, options?: AddEventListenerOptions): void; + removeEventListener(type: string, callback: JSEventListener | null, options?: EventListenerOptions): void; + dispatchEvent(event: Event): boolean; + new(): EventTarget; +} diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h new file mode 100644 index 0000000000..58cc9059f8 --- /dev/null +++ b/bridge/core/dom/events/event_target.h @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_EVENT_TARGET_H +#define KRAKENBRIDGE_EVENT_TARGET_H + +#include "bindings/qjs/cppgc/member.h" +#include "bindings/qjs/js_event_listener.h" +#include "bindings/qjs/qjs_function.h" +#include "bindings/qjs/script_wrappable.h" +#include "core/dom/binding_object.h" +#include "event_listener_map.h" +#include "foundation/logging.h" +#include "foundation/native_string.h" +#include "qjs_add_event_listener_options.h" + +#if UNIT_TEST +void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); +#endif + +#define GetPropertyMagic "%g" +#define SetPropertyMagic "%s" + +namespace kraken { + +enum class DispatchEventResult { + // Event was not canceled by event handler or default event handler. + kNotCanceled, + // Event was canceled by event handler; i.e. a script handler calling + // preventDefault. + kCanceledByEventHandler, + // Event was canceled by the default event handler; i.e. executing the default + // action. This result should be used sparingly as it deviates from the DOM + // Event Dispatch model. Default event handlers really shouldn't be invoked + // inside of dispatch. + kCanceledByDefaultEventHandler, + // Event was canceled but suppressed before dispatched to event handler. This + // result should be used sparingly; and its usage likely indicates there is + // potential for a bug. Trusted events may return this code; but untrusted + // events likely should always execute the event handler the developer intends + // to execute. + kCanceledBeforeDispatch, +}; + +struct FiringEventIterator { + KRAKEN_DISALLOW_NEW(); + + public: + FiringEventIterator(const AtomicString& event_type, size_t& iterator, size_t& end) + : event_type(event_type), iterator(iterator), end(end) {} + + const AtomicString& event_type; + size_t& iterator; + size_t& end; +}; + +using FiringEventIteratorVector = std::vector<FiringEventIterator>; + +class EventTargetData final { + KRAKEN_DISALLOW_NEW(); + + public: + EventTargetData(); + EventTargetData(const EventTargetData&) = delete; + EventTargetData& operator=(const EventTargetData&) = delete; + ~EventTargetData(); + + void Trace(GCVisitor* visitor) const; + + EventListenerMap event_listener_map; + std::unique_ptr<FiringEventIteratorVector> firing_event_iterators; +}; + +// All DOM event targets extend EventTarget. The spec is defined here: +// https://dom.spec.whatwg.org/#interface-eventtarget +// EventTarget objects allow us to add and remove an event +// listeners of a specific event type. Each EventTarget object also represents +// the target to which an event is dispatched when something has occurred. +class EventTarget : public ScriptWrappable, public BindingObject { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = EventTarget*; + + static EventTarget* Create(ExecutingContext* context, ExceptionState& exception_state); + + EventTarget() = delete; + ~EventTarget(); + explicit EventTarget(ExecutingContext* context); + + bool addEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& event_listener, + const std::shared_ptr<AddEventListenerOptions>& options, + ExceptionState& exception_state); + bool addEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& event_listener, + ExceptionState& exception_state); + bool removeEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& event_listener, + ExceptionState& exception_state); + bool removeEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& event_listener, + const std::shared_ptr<EventListenerOptions>& options, + ExceptionState& exception_state); + bool removeEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& event_listener, + bool use_capture, + ExceptionState& exception_state); + bool dispatchEvent(Event* event, ExceptionState& exception_state); + + DispatchEventResult FireEventListeners(Event&, ExceptionState&); + + static DispatchEventResult GetDispatchEventResult(const Event&); + + int32_t eventTargetId() const { return event_target_id_; } + + virtual bool IsWindowOrWorkerGlobalScope() const { return false; } + + protected: + virtual bool AddEventListenerInternal(const AtomicString& event_type, + const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<AddEventListenerOptions>& options); + bool RemoveEventListenerInternal(const AtomicString& event_type, + const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<EventListenerOptions>& options); + + DispatchEventResult DispatchEventInternal(Event& event, ExceptionState& exception_state); + + NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const override; + + // Subclasses should likely not override these themselves; instead, they + // should subclass EventTargetWithInlineData. + virtual EventTargetData* GetEventTargetData() = 0; + virtual EventTargetData& EnsureEventTargetData() = 0; + + private: + int32_t event_target_id_; + bool FireEventListeners(Event&, EventTargetData*, EventListenerVector&, ExceptionState&); +}; + +// Provide EventTarget with inlined EventTargetData for improved performance. +class EventTargetWithInlineData : public EventTarget { + public: + EventTargetWithInlineData() = delete; + explicit EventTargetWithInlineData(ExecutingContext* context) : EventTarget(context){}; + + void Trace(GCVisitor* visitor) const override; + + protected: + EventTargetData* GetEventTargetData() final { return &data_; } + EventTargetData& EnsureEventTargetData() final { return data_; } + + private: + EventTargetData data_; +}; + +// Macros to define an attribute event listener. +// |lower_name| - Lower-cased event type name. e.g. |focus| +// |symbol_name| - C++ symbol name in event_type_names namespace. e.g. |kFocus| +#define DEFINE_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + EventListener* on##lower_name() { return GetAttributeEventListener(event_type_names::symbol_name); } \ + void setOn##lower_name(EventListener* listener) { \ + SetAttributeEventListener(event_type_names::symbol_name, listener); \ + } + +#define DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + static EventListener* on##lower_name(EventTarget& eventTarget) { \ + return eventTarget.GetAttributeEventListener(event_type_names::symbol_name); \ + } \ + static void setOn##lower_name(EventTarget& eventTarget, EventListener* listener) { \ + eventTarget.SetAttributeEventListener(event_type_names::symbol_name, listener); \ + } + +#define DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + EventListener* on##lower_name() { \ + return GetDocument().GetWindowAttributeEventListener(event_type_names::symbol_name); \ + } \ + void setOn##lower_name(EventListener* listener) { \ + GetDocument().SetWindowAttributeEventListener(event_type_names::symbol_name, listener); \ + } + +#define DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + static EventListener* on##lower_name(EventTarget& eventTarget) { \ + if (Node* node = eventTarget.ToNode()) { \ + return node->GetDocument().GetWindowAttributeEventListener(event_type_names::symbol_name); \ + } \ + DCHECK(eventTarget.ToLocalDOMWindow()); \ + return eventTarget.GetAttributeEventListener(event_type_names::symbol_name); \ + } \ + static void setOn##lower_name(EventTarget& eventTarget, EventListener* listener) { \ + if (Node* node = eventTarget.ToNode()) { \ + node->GetDocument().SetWindowAttributeEventListener(event_type_names::symbol_name, listener); \ + } else { \ + DCHECK(eventTarget.ToLocalDOMWindow()); \ + eventTarget.SetAttributeEventListener(event_type_names::symbol_name, listener); \ + } \ + } + +// +// using NativeDispatchEvent = int32_t (*)(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* +// eventType, void* nativeEvent, int32_t isCustomEvent); using InvokeBindingMethod = void (*)(void* nativePtr, +// NativeValue* returnValue, NativeString* method, int32_t argc, NativeValue* argv); +// +// struct NativeEventTarget { +// NativeEventTarget() = delete; +// explicit NativeEventTarget(EventTargetInstance* _instance) : instance(_instance), +// dispatchEvent(reinterpret_cast<NativeDispatchEvent>(NativeEventTarget::dispatchEventImpl)){}; +// +// // Add more memory valid check with contextId. +// static int32_t dispatchEventImpl(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* eventType, +// void* nativeEvent, int32_t isCustomEvent); EventTargetInstance* instance{nullptr}; NativeDispatchEvent +// dispatchEvent{nullptr}; +//#if UNIT_TEST +// InvokeBindingMethod invokeBindingMethod{reinterpret_cast<InvokeBindingMethod>(TEST_invokeBindingMethod)}; +//#else +// InvokeBindingMethod invokeBindingMethod{nullptr}; +//#endif +//}; +// +// class EventTargetProperties : public HeapHashMap<JSAtom> { +// public: +// EventTargetProperties(JSContext* ctx) : HeapHashMap<JSAtom>(ctx){}; +//}; +// +// class EventHandlerMap : public HeapHashMap<JSAtom> { +// public: +// EventHandlerMap(JSContext* ctx) : HeapHashMap<JSAtom>(ctx){}; +//}; +// +// class EventTargetInstance : public Instance { +// public: +// EventTargetInstance() = delete; +// explicit EventTargetInstance(EventTarget* eventTarget, JSClassID classId, JSClassExoticMethods& exoticMethods, +// std::string name); explicit EventTargetInstance(EventTarget* eventTarget, JSClassID classId, std::string name); +// explicit EventTargetInstance(EventTarget* eventTarget, JSClassID classId, std::string name, int64_t eventTargetId); +// ~EventTargetInstance(); +// +// virtual bool dispatchEvent(EventInstance* event); +// static inline JSClassID classId(); +// inline int32_t eventTargetId() const { return m_eventTargetId; } +// +// // @TODO: Should move to BindingObject. +// JSValue invokeBindingMethod(const char* method, int32_t argc, NativeValue* argv); +// JSValue getBindingProperty(const char* prop); +// void setBindingProperty(const char* prop, NativeValue value); +// +// NativeEventTarget* nativeEventTarget{new NativeEventTarget(this)}; +// +// protected: +// int32_t m_eventTargetId; +// // EventListener handlers registered with addEventListener API. +// // https://dom.spec.whatwg.org/#concept-event-listener +// EventListenerMap m_eventListenerMap{m_ctx}; +// +// // EventListener handlers registered with DOM attributes API. +// // https://html.spec.whatwg.org/C/#event-handler-attributes +// EventHandlerMap m_eventHandlerMap{m_ctx}; +// +// // When javascript code set a property on EventTarget instance, EventTarget::kSetAttribute callback will be called +// when +// // property are not defined by Object.defineProperty or kSetAttribute. +// // We store there values in here. +// EventTargetProperties m_properties{m_ctx}; +// +// void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; +// static void copyNodeProperties(EventTargetInstance* newNode, EventTargetInstance* referenceNode); +// +// static int hasProperty(JSContext* ctx, JSValueConst obj, JSAtom atom); +// static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); +// static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int +// flags); static int deleteProperty(JSContext* ctx, JSValueConst obj, JSAtom prop); +// +// // Used for legacy "onEvent" attribute APIs. +// void setAttributesEventHandler(JSString* p, JSValue value); +// JSValue getAttributesEventHandler(JSString* p); +// +// private: +// bool internalDispatchEvent(EventInstance* eventInstance); +// static void finalize(JSRuntime* rt, JSValue val); +// friend EventTarget; +// friend StyleDeclarationInstance; +//}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_EVENT_TARGET_H diff --git a/bridge/core/dom/events/event_target_impl.cc b/bridge/core/dom/events/event_target_impl.cc new file mode 100644 index 0000000000..05a255735c --- /dev/null +++ b/bridge/core/dom/events/event_target_impl.cc @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "event_target_impl.h" diff --git a/bridge/core/dom/events/event_target_impl.h b/bridge/core/dom/events/event_target_impl.h new file mode 100644 index 0000000000..e01a2795b4 --- /dev/null +++ b/bridge/core/dom/events/event_target_impl.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ +#define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ + +#include "event_target.h" + +namespace kraken { + +// Constructible version of EventTarget. Calls to EventTarget +// constructor in JavaScript will return an instance of this class. +// We don't use EventTarget directly because EventTarget is an abstract +// class and and making it non-abstract is unfavorable because it will +// increase the size of EventTarget and all of its subclasses with code +// that are mostly unnecessary for them, resulting in a performance +// decrease. +class EventTargetImpl : public EventTarget {}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc new file mode 100644 index 0000000000..75f6e91e60 --- /dev/null +++ b/bridge/core/dom/events/event_target_test.cc @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "event_target.h" +#include "gtest/gtest.h" +#include "kraken_test_env.h" + +using namespace kraken; + +TEST(EventTarget, addEventListener) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "1234"); + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f); " + "div.dispatchEvent(new Event('click'));"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} + +TEST(EventTarget, removeEventListener) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f);" + "div.removeEventListener('click', f); div.dispatchEvent(new Event('click'));"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(logCalled, false); +} +// +// TEST(EventTarget, setNoEventTargetProperties) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "{name: 1}"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// +// auto context = bridge->GetExecutingContext(); +// const char* code = +// "let div = document.createElement('div'); div._a = { name: 1}; console.log(div._a); " +// "document.body.appendChild(div);"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// EXPECT_EQ(errorCalled, false); +//} +// +// TEST(EventTarget, propertyEventHandler) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "ƒ () 1234"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->GetExecutingContext(); +// const char* code = +// "let div = document.createElement('div'); " +// "div.onclick = function() { return 1234; };" +// "document.body.appendChild(div);" +// "let f = div.onclick;" +// "console.log(f, div.onclick());"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +// TEST(EventTarget, setUnExpectedAttributeEventHandler) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = false; +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->GetExecutingContext(); +// const char* code = +// "let div = document.createElement('div'); " +// "div.onclick = function() { return 1234; };" +// "document.body.appendChild(div);" +// "div.onclick = undefined;" +// "div.click()"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, false); +//} +// +// TEST(EventTarget, propertyEventOnWindow) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "1234"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->GetExecutingContext(); +// const char* code = +// "window.onclick = function() { console.log(1234); };" +// "window.dispatchEvent(new Event('click'));"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +// TEST(EventTarget, asyncFunctionCallback) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "done"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->GetExecutingContext(); +// std::string code = R"( +// const img = document.createElement('img'); +// img.style.width = '100px'; +// img.style.height = '100px'; +// img.src = "assets/kraken.png"; +// document.body.appendChild(img); +// const img2 = img.cloneNode(false); +// document.body.appendChild(img2); +// +// let anotherImgHasLoad = false; +// async function loadImg() { +// if (anotherImgHasLoad) { +// console.log('done'); +// } else { +// anotherImgHasLoad = true; +// } +// } +// +// img.addEventListener('load', loadImg); +// img2.addEventListener('load', loadImg); +// +// img.dispatchEvent(new Event('load')); +// img2.dispatchEvent(new Event('load')); +//)"; +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +// TEST(EventTarget, ClassInheritEventTarget) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "ƒ () ƒ ()"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->GetExecutingContext(); +// std::string code = std::string(R"( +// class Sample extends EventTarget { +// constructor() { +// super(); +// } +//} +// +// let s = new Sample(); +// console.log(s.addEventListener, s.removeEventListener) +//)"); +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +// TEST(EventTarget, wontLeakWithStringProperty) { +// auto bridge = TEST_init(); +// std::string code = +// "var img = new Image();\n" +// "img.any = '1234'"; +// bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); +//} +// +// TEST(EventTarget, dispatchEventOnGC) { +// using namespace kraken; +// +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "1234"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); +// auto context = bridge->GetExecutingContext(); +// std::string code = std::string(R"( +//{ +//// Wrap div in a block scope will be freed by GC +// let div = document.createElement('div'); +//} +// window.onclick = () => {console.log(1234);} +// +// setTimeout(() => {}); +//)"); +// +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// +// static auto* window = static_cast<EventTargetInstance*>(JS_GetOpaque(context->global(), 1)); +// static int32_t contextId = context->getContextId(); +// +// TEST_registerEventTargetDisposedCallback(context->uniqueId, [](EventTargetInstance* eventTargetInstance) { +// // Check to not crash when trigger click on disposed eventTarget +// TEST_dispatchEvent(contextId, eventTargetInstance, "click"); +// +// // Check to not crash when trigger event on any eventTarget. +// TEST_dispatchEvent(contextId, window, "click"); +// }); +// +// // Run gc to trigger eventTarget been disposed by GC. +// JS_RunGC(context->runtime()); +// +// TEST_runLoop(context); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +// TEST(EventTarget, globalBindListener) { +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "clicked"); +// }; +// auto bridge = TEST_init(); +// std::string code = "addEventListener('click', () => {console.log('clicked'); }); dispatchEvent(new Event('click'))"; +// bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); +// EXPECT_EQ(logCalled, true); +//} +// +// TEST(EventTarget, shouldKeepAtom) { +// auto bridge = TEST_init(); +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "2"); +// }; +// std::string code = "addEventListener('click', () => {console.log(1)});"; +// bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); +// JS_RunGC(bridge->GetExecutingContext()->runtime()); +// +// std::string code2 = "addEventListener('appear', () => {console.log(2)});"; +// bridge->evaluateScript(code2.c_str(), code2.size(), "internal://", 0); +// +// JS_RunGC(bridge->GetExecutingContext()->runtime()); +// +// std::string code3 = "(function() { var eeee = new Event('appear'); dispatchEvent(eeee); } )();"; +// bridge->evaluateScript(code3.c_str(), code3.size(), "internal://", 0); +// EXPECT_EQ(logCalled, true); +//} diff --git a/bridge/bindings/qjs/dom/event_test.cc b/bridge/core/dom/events/event_test.cc similarity index 84% rename from bridge/bindings/qjs/dom/event_test.cc rename to bridge/core/dom/events/event_test.cc index ab3ad19193..b05b4f25f1 100644 --- a/bridge/bindings/qjs/dom/event_test.cc +++ b/bridge/core/dom/events/event_test.cc @@ -16,7 +16,8 @@ TEST(MouseEvent, init) { }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); auto context = bridge->getContext(); - const char* code = "let mouseEvent = new MouseEvent('click', {clientX: 10, clientY: 20}); console.log(mouseEvent.clientX);"; + const char* code = + "let mouseEvent = new MouseEvent('click', {clientX: 10, clientY: 20}); console.log(mouseEvent.clientX);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); diff --git a/bridge/core/dom/events/registered_eventListener.cc b/bridge/core/dom/events/registered_eventListener.cc new file mode 100644 index 0000000000..3085cbd841 --- /dev/null +++ b/bridge/core/dom/events/registered_eventListener.cc @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "registered_eventListener.h" +#include "qjs_add_event_listener_options.h" + +namespace kraken { + +RegisteredEventListener::RegisteredEventListener() = default; + +RegisteredEventListener::RegisteredEventListener(const std::shared_ptr<EventListener>& listener, + std::shared_ptr<AddEventListenerOptions> options) + : callback_(listener), + use_capture_(options->capture()), + passive_(options->passive()), + once_(options->once()), + blocked_event_warning_emitted_(false){}; + +RegisteredEventListener::RegisteredEventListener(const RegisteredEventListener& that) = default; + +RegisteredEventListener& RegisteredEventListener::operator=(const RegisteredEventListener& that) = default; + +void RegisteredEventListener::SetCallback(const std::shared_ptr<EventListener>& listener) { + callback_ = listener; +} + +bool RegisteredEventListener::Matches(const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<EventListenerOptions>& options) const { + // Equality is soley based on the listener and useCapture flags. + assert(callback_); + assert(listener); + return callback_->Matches(*listener) && static_cast<bool>(use_capture_) == options->capture(); +} + +bool RegisteredEventListener::ShouldFire(const Event& event) const { + if (event.FireOnlyCaptureListenersAtTarget()) { + assert(event.eventPhase() == Event::kAtTarget); + return Capture(); + } + if (event.FireOnlyNonCaptureListenersAtTarget()) { + assert(event.eventPhase() == Event::kAtTarget); + return !Capture(); + } + if (event.eventPhase() == Event::kCapturingPhase) + return Capture(); + if (event.eventPhase() == Event::kBubblingPhase) + return !Capture(); + return true; +} + +void RegisteredEventListener::Trace(GCVisitor* visitor) const { + callback_->Trace(visitor); +} + +bool operator==(const RegisteredEventListener& lhs, const RegisteredEventListener& rhs) { + assert(lhs.Callback()); + assert(rhs.Callback()); + return lhs.Callback()->Matches(*rhs.Callback()) && lhs.Capture() == rhs.Capture(); +} + +} // namespace kraken diff --git a/bridge/core/dom/events/registered_eventListener.h b/bridge/core/dom/events/registered_eventListener.h new file mode 100644 index 0000000000..ac995f6d58 --- /dev/null +++ b/bridge/core/dom/events/registered_eventListener.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ +#define KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ + +#include "event_listener.h" +#include "foundation/macros.h" + +namespace kraken { + +class AddEventListenerOptions; +class EventListenerOptions; + +// RegisteredEventListener represents 'event listener' defined in the DOM +// standard. https://dom.spec.whatwg.org/#concept-event-listener +class RegisteredEventListener final { + KRAKEN_DISALLOW_NEW() + public: + RegisteredEventListener(); + RegisteredEventListener(const std::shared_ptr<EventListener>& listener, + std::shared_ptr<AddEventListenerOptions> options); + RegisteredEventListener(const RegisteredEventListener& that); + RegisteredEventListener& operator=(const RegisteredEventListener& that); + + const std::shared_ptr<EventListener> Callback() const { return callback_; } + void SetCallback(const std::shared_ptr<EventListener>& listener); + + void SetCallback(EventListener* listener); + + bool Passive() const { return passive_; } + + bool Once() const { return once_; } + + bool Capture() const { return use_capture_; } + + bool BlockedEventWarningEmitted() const { return blocked_event_warning_emitted_; } + + void SetBlockedEventWarningEmitted() { blocked_event_warning_emitted_ = true; } + + bool Matches(const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<EventListenerOptions>& options) const; + + bool ShouldFire(const Event&) const; + + void Trace(GCVisitor* visitor) const; + + private: + std::shared_ptr<EventListener> callback_; + unsigned use_capture_ : 1; + unsigned passive_ : 1; + unsigned once_ : 1; + unsigned blocked_event_warning_emitted_ : 1; + + private: +}; + +bool operator==(const RegisteredEventListener&, const RegisteredEventListener&); + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ diff --git a/bridge/core/dom/frame_request_callback_collection.cc b/bridge/core/dom/frame_request_callback_collection.cc new file mode 100644 index 0000000000..d73046c91b --- /dev/null +++ b/bridge/core/dom/frame_request_callback_collection.cc @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "frame_request_callback_collection.h" + +#include <utility> +#include "bindings/qjs/cppgc/gc_visitor.h" + +namespace kraken { + +std::shared_ptr<FrameCallback> FrameCallback::Create(ExecutingContext* context, + const std::shared_ptr<QJSFunction>& callback) { + return std::make_shared<FrameCallback>(context, callback); +} + +FrameCallback::FrameCallback(ExecutingContext* context, std::shared_ptr<QJSFunction> callback) + : context_(context), callback_(std::move(callback)) {} + +void FrameCallback::Fire(double highResTimeStamp) { + if (callback_ == nullptr) + return; + + JSContext* ctx = context_->ctx(); + + ScriptValue arguments[] = {ScriptValue(ctx, highResTimeStamp)}; + + ScriptValue return_value = callback_->Invoke(ctx, ScriptValue::Empty(ctx), 1, arguments); + + context_->DrainPendingPromiseJobs(); + if (return_value.IsException()) { + context_->HandleException(&return_value); + } +} + +void FrameCallback::Trace(GCVisitor* visitor) const { + callback_->Trace(visitor); +} + +void FrameRequestCallbackCollection::RegisterFrameCallback(uint32_t callback_id, + const std::shared_ptr<FrameCallback>& frame_callback) { + frameCallbacks_[callback_id] = frame_callback; +} + +void FrameRequestCallbackCollection::CancelFrameCallback(uint32_t callbackId) { + if (frameCallbacks_.count(callbackId) == 0) + return; + frameCallbacks_.erase(callbackId); +} + +void FrameRequestCallbackCollection::Trace(GCVisitor* visitor) const { + for (auto& entry : frameCallbacks_) { + entry.second->Trace(visitor); + } +} + +} // namespace kraken diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h new file mode 100644 index 0000000000..c06b9c5b85 --- /dev/null +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ + +#include "core/executing_context.h" + +namespace kraken { + +// |FrameCallback| is an interface type which generalizes callbacks which are +// invoked when a script-based animation needs to be resampled. +class FrameCallback { + public: + static std::shared_ptr<FrameCallback> Create(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback); + + FrameCallback(ExecutingContext* context, std::shared_ptr<QJSFunction> callback); + + void Fire(double highResTimeStamp); + + ExecutingContext* context() { return context_; }; + + void Trace(GCVisitor* visitor) const; + + private: + std::shared_ptr<QJSFunction> callback_; + ExecutingContext* context_{nullptr}; +}; + +class FrameRequestCallbackCollection final { + public: + void RegisterFrameCallback(uint32_t callback_id, const std::shared_ptr<FrameCallback>& frame_callback); + void CancelFrameCallback(uint32_t callback_id); + + void Trace(GCVisitor* visitor) const; + + private: + std::unordered_map<uint32_t, std::shared_ptr<FrameCallback>> frameCallbacks_; +}; + +} // namespace kraken + +class frame_request_callback_collection {}; + +#endif // KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ diff --git a/bridge/core/dom/legacy/bounding_client_rect.cc b/bridge/core/dom/legacy/bounding_client_rect.cc new file mode 100644 index 0000000000..016bad6cd4 --- /dev/null +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "bounding_client_rect.h" +#include "core/executing_context.h" + +namespace kraken { + +BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, + NativeBoundingClientRect* native_bounding_client_rect) { + return MakeGarbageCollected<BoundingClientRect>(context, native_bounding_client_rect); +} + +BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, ExceptionState& exceptionState) { + return nullptr; +} + +BoundingClientRect::BoundingClientRect(ExecutingContext* context, NativeBoundingClientRect* nativeBoundingClientRect) + : ScriptWrappable(context->ctx()), + x_(nativeBoundingClientRect->x), + y_(nativeBoundingClientRect->y), + width_(nativeBoundingClientRect->width), + height_(nativeBoundingClientRect->height), + top_(nativeBoundingClientRect->top), + right_(nativeBoundingClientRect->right), + left_(nativeBoundingClientRect->left), + bottom_(nativeBoundingClientRect->bottom) {} + +void BoundingClientRect::Trace(GCVisitor* visitor) const {} + +} // namespace kraken diff --git a/bridge/core/dom/legacy/bounding_client_rect.d.ts b/bridge/core/dom/legacy/bounding_client_rect.d.ts new file mode 100644 index 0000000000..26fe6df07a --- /dev/null +++ b/bridge/core/dom/legacy/bounding_client_rect.d.ts @@ -0,0 +1,12 @@ +interface BoundingClientRect { + readonly x: double; + readonly y: double; + readonly width: double; + readonly height: double; + readonly top: double; + readonly right: double; + readonly bottom: double; + readonly left: double; + + new(): void; +} diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h new file mode 100644 index 0000000000..e5ae417dea --- /dev/null +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ +#define KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ + +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/script_wrappable.h" + +namespace kraken { + +class ExecutingContext; + +struct NativeBoundingClientRect { + double x; + double y; + double width; + double height; + double top; + double right; + double bottom; + double left; +}; + +class BoundingClientRect : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + BoundingClientRect() = delete; + static BoundingClientRect* Create(ExecutingContext* context, NativeBoundingClientRect* native_bounding_client_rect); + static BoundingClientRect* Create(ExecutingContext* context, ExceptionState& exceptionState); + explicit BoundingClientRect(ExecutingContext* context, NativeBoundingClientRect* nativeBoundingClientRect); + + void Trace(GCVisitor* visitor) const override; + + double x() const { return x_; } + double y() const { return y_; } + double width() const { return width_; } + double height() const { return height_; } + double top() const { return top_; } + double right() const { return right_; } + double bottom() const { return bottom_; } + double left() const { return left_; } + + private: + double x_; + double y_; + double width_; + double height_; + double top_; + double right_; + double bottom_; + double left_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc new file mode 100644 index 0000000000..6d622fcce1 --- /dev/null +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "element_attributes.h" +#include "bindings/qjs/exception_state.h" +#include "built_in_string.h" +#include "core/dom/element.h" + +namespace kraken { + +static inline bool IsNumberIndex(const StringView& name) { + if (name.Empty()) + return false; + char f = name.Characters8()[0]; + return f >= '0' && f <= '9'; +} + +ElementAttributes::ElementAttributes(Element* element) : ScriptWrappable(element->ctx()) {} +ElementAttributes::ElementAttributes(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} + +AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { + bool numberIndex = IsNumberIndex(name.ToStringView()); + + if (numberIndex) { + AtomicString::Empty(ctx()); + } + + return attributes_[name]; +} + +bool ElementAttributes::setAttribute(const AtomicString& name, + const AtomicString& value, + ExceptionState& exception_state) { + bool numberIndex = IsNumberIndex(name.ToStringView()); + + if (numberIndex) { + exception_state.ThrowException( + ctx(), ErrorType::TypeError, + "Failed to execute 'kSetAttribute' on 'Element': '" + name.ToStdString() + "' is not a valid attribute name."); + return false; + } + + if (name == built_in_string::kclass) { + std::string v = value.ToStdString(); + class_name_->set(v); + } + + attributes_[name] = value; + + return true; +} + +bool ElementAttributes::hasAttribute(const AtomicString& name, ExceptionState& exception_state) { + bool numberIndex = IsNumberIndex(name.ToStringView()); + + if (numberIndex) { + return false; + } + + return attributes_.count(name) > 0; +} + +void ElementAttributes::removeAttribute(const AtomicString& name, ExceptionState& exception_state) { + attributes_.erase(name); +} + +void ElementAttributes::CopyWith(ElementAttributes* attributes) { + for (auto& attr : attributes->attributes_) { + attributes_[attr.first] = attr.second; + } +} + +std::shared_ptr<SpaceSplitString> ElementAttributes::ClassName() { + return class_name_; +} + +std::string ElementAttributes::ToString() { + std::string s; + + for (auto& attr : attributes_) { + s += attr.first.ToStdString() + "="; + s += "\"" + attr.second.ToStdString() + "\""; + } + + return s; +} + +bool ElementAttributes::IsEquivalent(const ElementAttributes& other) const { + if (attributes_.size() != other.attributes_.size()) + return false; + for (auto& entry : attributes_) { + auto it = other.attributes_.find(entry.first); + if (it == other.attributes_.end()) { + return false; + } + } + return true; +} + +void ElementAttributes::Trace(GCVisitor* visitor) const {} + +} // namespace kraken diff --git a/bridge/core/dom/legacy/element_attributes.d.ts b/bridge/core/dom/legacy/element_attributes.d.ts new file mode 100644 index 0000000000..b5ffd4b28a --- /dev/null +++ b/bridge/core/dom/legacy/element_attributes.d.ts @@ -0,0 +1,7 @@ +export interface ElementAttributes { + // Legacy methods: these methods are not W3C standard. + setAttribute(name: string, value: string): void; + hasAttribute(name: string): boolean; + removeAttribute(name: string): void; + new(): void; +} diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h new file mode 100644 index 0000000000..1857d8c9e2 --- /dev/null +++ b/bridge/core/dom/legacy/element_attributes.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ +#define KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ + +#include <unordered_map> +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/script_wrappable.h" +#include "space_split_string.h" + +namespace kraken { + +class ExceptionState; +class Element; + +// TODO: refactor for better W3C standard support and higher performance. +class ElementAttributes : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = ElementAttributes*; + + static ElementAttributes* Create(Element* element) { return MakeGarbageCollected<ElementAttributes>(element); } + static ElementAttributes* Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected<ElementAttributes>(context); + } + + ElementAttributes(Element) = delete; + ElementAttributes(Element* element); + ElementAttributes(ExecutingContext* context); + + AtomicString GetAttribute(const AtomicString& name); + 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); + void CopyWith(ElementAttributes* attributes); + std::shared_ptr<SpaceSplitString> ClassName(); + std::string ToString(); + + bool IsEquivalent(const ElementAttributes& other) const; + + void Trace(GCVisitor* visitor) const override; + + private: + std::unordered_map<AtomicString, AtomicString, AtomicString::KeyHasher> attributes_; + std::shared_ptr<SpaceSplitString> class_name_{std::make_shared<SpaceSplitString>("")}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ diff --git a/bridge/core/dom/legacy/space_split_string.cc b/bridge/core/dom/legacy/space_split_string.cc new file mode 100644 index 0000000000..a53fa721e1 --- /dev/null +++ b/bridge/core/dom/legacy/space_split_string.cc @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "space_split_string.h" + +namespace kraken { + +std::string SpaceSplitString::m_delimiter{" "}; + +void SpaceSplitString::set(std::string& string) { + size_t pos = 0; + std::string token; + std::string s = string; + while ((pos = s.find(m_delimiter)) != std::string::npos) { + token = s.substr(0, pos); + m_szData.push_back(token); + s.erase(0, pos + m_delimiter.length()); + } + m_szData.push_back(s); +} + +bool SpaceSplitString::contains(std::string& string) { + for (std::string& s : m_szData) { + if (s == string) { + return true; + } + } + return false; +} + +bool SpaceSplitString::containsAll(std::string s) { + std::vector<std::string> szData; + size_t pos = 0; + std::string token; + + while ((pos = s.find(m_delimiter)) != std::string::npos) { + token = s.substr(0, pos); + szData.push_back(token); + s.erase(0, pos + m_delimiter.length()); + } + szData.push_back(s); + + bool flag = true; + for (std::string& str : szData) { + bool isContains = false; + for (std::string& data : m_szData) { + if (data == str) { + isContains = true; + break; + } + } + flag &= isContains; + } + + return flag; +} + +} // namespace kraken diff --git a/bridge/core/dom/legacy/space_split_string.h b/bridge/core/dom/legacy/space_split_string.h new file mode 100644 index 0000000000..d7e40492a1 --- /dev/null +++ b/bridge/core/dom/legacy/space_split_string.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ +#define KRAKENBRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ + +#include <string> +#include <vector> + +namespace kraken { + +class SpaceSplitString { + public: + SpaceSplitString() = default; + explicit SpaceSplitString(std::string string) { set(string); } + + void set(std::string& string); + bool contains(std::string& string); + bool containsAll(std::string s); + + private: + static std::string m_delimiter; + std::vector<std::string> m_szData; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc new file mode 100644 index 0000000000..417b3943aa --- /dev/null +++ b/bridge/core/dom/node.cc @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "node.h" +#include <unordered_map> +#include "character_data.h" +#include "child_node_list.h" +#include "document.h" +#include "document_fragment.h" +#include "empty_node_list.h" +#include "node_data.h" +#include "node_traversal.h" +#include "text.h" + +namespace kraken { + +Node* Node::Create(ExecutingContext* context, ExceptionState& exception_state) { + exception_state.ThrowException(context->ctx(), ErrorType::TypeError, "Illegal constructor"); + return nullptr; +} + +void Node::setNodeValue(const AtomicString& value, ExceptionState& exception_state) { + // By default, setting nodeValue has no effect. +} + +ContainerNode* Node::parentNode() const { + return ParentOrShadowHostNode(); +} + +Element* Node::parentElement() const { + return nullptr; +} + +NodeList* Node::childNodes() { + auto* this_node = DynamicTo<ContainerNode>(this); + if (this_node) + return EnsureNodeData().EnsureChildNodeList(*this_node); + return EnsureNodeData().EnsureEmptyChildNodeList(*this); +} + +//// Helper object to allocate EventTargetData which is otherwise only used +//// through EventTargetWithInlineData. +class EventTargetDataObject final { + public: + void Trace(GCVisitor* visitor) const { data_.Trace(visitor); } + EventTargetData& GetEventTargetData() { return data_; } + + private: + EventTargetData data_; +}; + +EventTargetData* Node::GetEventTargetData() { + return HasEventTargetData() ? &event_target_data_->GetEventTargetData() : nullptr; +} + +EventTargetData& Node::EnsureEventTargetData() { + if (HasEventTargetData()) + return event_target_data_->GetEventTargetData(); + assert(event_target_data_ == nullptr); + event_target_data_ = std::make_unique<EventTargetDataObject>(); + SetHasEventTargetData(true); + return event_target_data_->GetEventTargetData(); +} + +NodeData& Node::CreateNodeData() { + node_data_ = std::make_unique<NodeData>(); + SetFlag(kHasDataFlag); + return *Data(); +} + +NodeData& Node::EnsureNodeData() { + if (HasData()) + return *Data(); + return CreateNodeData(); +} + +Node& Node::TreeRoot() const { + const Node* node = this; + while (node->parentNode()) + node = node->parentNode(); + return const_cast<Node&>(*node); +} + +void Node::remove(ExceptionState& exception_state) { + if (ContainerNode* parent = parentNode()) + parent->RemoveChild(this, exception_state); +} + +Node* Node::insertBefore(Node* new_child, Node* ref_child, ExceptionState& exception_state) { + auto* this_node = DynamicTo<ContainerNode>(this); + if (this_node) + return this_node->InsertBefore(new_child, ref_child, exception_state); + + exception_state.ThrowException(ctx(), ErrorType::TypeError, "This node type does not support this method."); + return nullptr; +} + +Node* Node::replaceChild(Node* new_child, Node* old_child, ExceptionState& exception_state) { + auto* this_node = DynamicTo<ContainerNode>(this); + if (this_node) + return this_node->ReplaceChild(new_child, old_child, exception_state); + + exception_state.ThrowException(ctx(), ErrorType::TypeError, "This node type does not support this method."); + return nullptr; +} + +Node* Node::removeChild(Node* old_child, ExceptionState& exception_state) { + auto* this_node = DynamicTo<ContainerNode>(this); + if (this_node) + return this_node->RemoveChild(old_child, exception_state); + + exception_state.ThrowException(ctx(), ErrorType::TypeError, "This node type does not support this method."); + return nullptr; +} + +Node* Node::appendChild(Node* new_child, ExceptionState& exception_state) { + auto* this_node = DynamicTo<ContainerNode>(this); + if (LIKELY(this_node)) + return this_node->AppendChild(new_child, exception_state); + + exception_state.ThrowException(ctx(), ErrorType::TypeError, "This node type does not support this method."); + return nullptr; +} + +Node* Node::cloneNode(ExceptionState& exception_state) const { + return cloneNode(false, exception_state); +} + +Node* Node::cloneNode(bool deep, ExceptionState&) const { + // https://dom.spec.whatwg.org/#dom-node-clonenode + + // 2. Return a clone of this, with the clone children flag set if deep is + // true, and the clone shadows flag set if this is a DocumentFragment whose + // host is an HTML template element. + auto* fragment = DynamicTo<DocumentFragment>(this); + bool clone_shadows_flag = fragment && fragment->IsTemplateContent(); + return Clone(GetDocument(), + deep ? (clone_shadows_flag ? CloneChildrenFlag::kCloneWithShadows : CloneChildrenFlag::kClone) + : CloneChildrenFlag::kSkip); +} + +bool Node::isEqualNode(Node* other, ExceptionState& exception_state) const { + if (!other) + return false; + + NodeType node_type = nodeType(); + if (node_type != other->nodeType()) + return false; + + if (nodeValue() != other->nodeValue()) + return false; + + // if (auto* this_attr = DynamicTo<Attr>(this)) { + // auto* other_attr = To<Attr>(other); + // if (this_attr->localName() != other_attr->localName()) + // return false; + // + // if (this_attr->namespaceURI() != other_attr->namespaceURI()) + // return false; + // } else + + if (auto* this_element = DynamicTo<Element>(this)) { + auto* other_element = DynamicTo<Element>(other); + if (this_element->tagName() != other_element->tagName()) + return false; + + if (!this_element->HasEquivalentAttributes(*other_element)) + return false; + } else if (nodeName() != other->nodeName()) { + return false; + } + + Node* child = firstChild(); + Node* other_child = other->firstChild(); + + while (child) { + if (!child->isEqualNode(other_child)) + return false; + + child = child->nextSibling(); + other_child = other_child->nextSibling(); + } + + if (other_child) + return false; + + return true; +} + +bool Node::isEqualNode(Node* other) const { + ExceptionState exception_state; + return isEqualNode(other, exception_state); +} + +AtomicString Node::textContent(bool convert_brs_to_newlines) const { + // This covers ProcessingInstruction and Comment that should return their + // value when .textContent is accessed on them, but should be ignored when + // iterated over as a descendant of a ContainerNode. + if (auto* character_data = DynamicTo<CharacterData>(this)) + return character_data->data(); + + // Attribute nodes have their attribute values as textContent. + // if (auto* attr = DynamicTo<Attr>(this)) + // return attr->value(); + + // Documents and non-container nodes (that are not CharacterData) + // have null textContent. + if (IsDocumentNode() || !IsContainerNode()) + return AtomicString::Empty(ctx()); + + std::string content; + for (const Node& node : NodeTraversal::InclusiveDescendantsOf(*this)) { + if (auto* text_node = DynamicTo<Text>(node)) { + content.append(text_node->data().ToStdString()); + } + } + return AtomicString(ctx(), content); +} + +void Node::setTextContent(const AtomicString& text, ExceptionState& exception_state) { + switch (nodeType()) { + case kAttributeNode: + case kTextNode: + case kCommentNode: + setNodeValue(text, exception_state); + return; + case kElementNode: + case kDocumentFragmentNode: { + // FIXME: Merge this logic into replaceChildrenWithText. + auto* container = To<ContainerNode>(this); + + // Note: This is an intentional optimization. + // See crbug.com/352836 also. + // No need to do anything if the text is identical. + if (container->HasOneTextChild() && To<Text>(container->firstChild())->data() == text && !text.IsEmpty()) + return; + + // Note: This API will not insert empty text nodes: + // https://dom.spec.whatwg.org/#dom-node-textcontent + if (text.IsEmpty()) { + container->RemoveChildren(); + } else { + container->RemoveChildren(); + container->AppendChild(GetDocument().createTextNode(text, exception_state), exception_state); + } + return; + } + case kDocumentNode: + case kDocumentTypeNode: + // Do nothing. + return; + } +} + +void Node::SetCustomElementState(CustomElementState new_state) { + CustomElementState old_state = GetCustomElementState(); + + switch (new_state) { + case CustomElementState::kUncustomized: + return; + + case CustomElementState::kUndefined: + assert(CustomElementState::kUncustomized == old_state); + break; + + case CustomElementState::kCustom: + assert(old_state == CustomElementState::kUndefined || old_state == CustomElementState::kFailed || + old_state == CustomElementState::kPreCustomized); + break; + + case CustomElementState::kFailed: + assert(CustomElementState::kFailed != old_state); + break; + + case CustomElementState::kPreCustomized: + assert(CustomElementState::kFailed == old_state); + break; + } + + assert(IsHTMLElement()); + + auto* element = To<Element>(this); + node_flags_ = (node_flags_ & ~kCustomElementStateMask) | static_cast<NodeFlags>(new_state); + assert(new_state == GetCustomElementState()); +} + +bool Node::IsDocumentNode() const { + return this == &GetDocument(); +} + +Element* Node::ParentOrShadowHostElement() const { + ContainerNode* parent = ParentOrShadowHostNode(); + if (!parent) + return nullptr; + + return DynamicTo<Element>(parent); +} + +void Node::InsertedInto(ContainerNode& insertion_point) { + assert(insertion_point.isConnected() || IsContainerNode()); + if (insertion_point.isConnected()) { + SetFlag(kIsConnectedFlag); + insertion_point.GetDocument().IncrementNodeCount(); + } +} + +void Node::RemovedFrom(ContainerNode& insertion_point) { + assert(insertion_point.isConnected() || IsContainerNode()); + if (insertion_point.isConnected()) { + ClearFlag(kIsConnectedFlag); + insertion_point.GetDocument().DecrementNodeCount(); + } +} + +ContainerNode* Node::NonShadowBoundaryParentNode() const { + return parentNode(); +} + +unsigned int Node::NodeIndex() const { + const Node* temp_node = previousSibling(); + unsigned count = 0; + for (count = 0; temp_node; count++) + temp_node = temp_node->previousSibling(); + return count; +} + +Document* Node::ownerDocument() const { + Document* doc = &GetDocument(); + return doc == this ? nullptr : doc; +} + +bool Node::IsDescendantOf(const Node* other) const { + // Return true if other is an ancestor of this, otherwise false + if (!other || isConnected() != other->isConnected()) + return false; + if (&other->GetDocument() != &GetDocument()) + return false; + for (const ContainerNode* n = parentNode(); n; n = n->parentNode()) { + if (n == other) + return true; + } + return false; +} + +bool Node::contains(const Node* node, ExceptionState& exception_state) const { + if (!node) + return false; + return this == node || node->IsDescendantOf(this); +} + +bool Node::ContainsIncludingHostElements(const Node& node) const { + const Node* current = &node; + do { + if (current == this) + return true; + auto* curr_fragment = DynamicTo<DocumentFragment>(current); + current = current->ParentOrShadowHostNode(); + } while (current); + return false; +} + +Node* Node::CommonAncestor(const Node& other, ContainerNode* (*parent)(const Node&)) const { + if (this == &other) + return const_cast<Node*>(this); + if (&GetDocument() != &other.GetDocument()) + return nullptr; + int this_depth = 0; + for (const Node* node = this; node; node = parent(*node)) { + if (node == &other) + return const_cast<Node*>(node); + this_depth++; + } + int other_depth = 0; + for (const Node* node = &other; node; node = parent(*node)) { + if (node == this) + return const_cast<Node*>(this); + other_depth++; + } + const Node* this_iterator = this; + const Node* other_iterator = &other; + if (this_depth > other_depth) { + for (int i = this_depth; i > other_depth; --i) + this_iterator = parent(*this_iterator); + } else if (other_depth > this_depth) { + for (int i = other_depth; i > this_depth; --i) + other_iterator = parent(*other_iterator); + } + while (this_iterator) { + if (this_iterator == other_iterator) + return const_cast<Node*>(this_iterator); + this_iterator = parent(*this_iterator); + other_iterator = parent(*other_iterator); + } + assert(!other_iterator); + return nullptr; +} + +Node::Node(ExecutingContext* context, TreeScope* tree_scope, ConstructionType type) + : EventTarget(context), + node_flags_(type), + parent_or_shadow_host_node_(nullptr), + previous_(nullptr), + tree_scope_(tree_scope), + next_(nullptr), + node_data_(nullptr) {} + +Node::~Node() {} + +void Node::Trace(GCVisitor* visitor) const { + visitor->Trace(previous_); + visitor->Trace(next_); + visitor->Trace(parent_or_shadow_host_node_); + if (node_data_ != nullptr) + node_data_->Trace(visitor); + if (event_target_data_ != nullptr) { + event_target_data_->Trace(visitor); + } + EventTarget::Trace(visitor); +} + +} // namespace kraken diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts new file mode 100644 index 0000000000..aff9dfd9fe --- /dev/null +++ b/bridge/core/dom/node.d.ts @@ -0,0 +1,75 @@ +import { EventTarget } from './events/event_target'; +import { Document } from './document'; +import {Element} from "./element"; +import {NodeList} from "./node_list"; + +/** Node is an interface from which a number of DOM API object types inherit. It allows those types to be treated similarly; for example, inheriting the same set of methods, or being tested in the same way. */ +interface Node extends EventTarget { + /** + * Returns the children. + */ + readonly childNodes: NodeList; + /** + * Returns the first child. + */ + readonly firstChild: Node | null; + /** + * Returns true if node is connected and false otherwise. + */ + readonly isConnected: boolean; + /** + * Returns the last child. + */ + readonly lastChild: Node | null; + /** + * Returns the next sibling. + */ + readonly nextSibling: Node | null; + /** + * Returns a string appropriate for the type of node. + */ + readonly nodeName: string; + /** + * Returns the type of node. + */ + readonly nodeType: number; + nodeValue: string | null; + /** + * Returns the node document. Returns null for documents. + */ + readonly ownerDocument: Document | null; + /** + * Returns the parent element. + */ + // @ts-ignore + readonly parentElement: Element | null; + /** + * Returns the parent. + */ + readonly parentNode: Node | null; + /** + * Returns the previous sibling. + */ + readonly previousSibling: Node | null; + textContent: string | null; + appendChild(newNode: Node): Node; + /** + * Returns a copy of node. If deep is true, the copy also includes the node's descendants. + */ + cloneNode(deep?: boolean): Node; + /** + * Returns true if other is an inclusive descendant of node, and false otherwise. + */ + contains(other: Node | null): boolean; + insertBefore(newChild: Node, refChild: Node | null): Node; + /** + * Returns whether node and otherNode have the same properties. + */ + isEqualNode(otherNode: Node | null): boolean; + isSameNode(otherNode: Node | null): boolean; + removeChild(oldChild: Node): Node; + remove(): void; + replaceChild(newChild: Node, oldChild: Node): Node; + + new(): void; +} diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h new file mode 100644 index 0000000000..3a835110fb --- /dev/null +++ b/bridge/core/dom/node.h @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_NODE_H +#define KRAKENBRIDGE_NODE_H + +#include <set> +#include <utility> + +#include "events/event_target.h" +#include "foundation/macros.h" +#include "node_data.h" +#include "tree_scope.h" + +namespace kraken { + +const int kDOMNodeTypeShift = 2; +const int kElementNamespaceTypeShift = 4; +const int kNodeStyleChangeShift = 15; +const int kNodeCustomElementShift = 17; + +class Element; +class Document; +class DocumentFragment; +class ContainerNode; +class NodeList; +class EventTargetDataObject; + +enum class CustomElementState : uint32_t { + // https://dom.spec.whatwg.org/#concept-element-custom-element-state + kUncustomized = 0, + kCustom = 1 << kNodeCustomElementShift, + kPreCustomized = 2 << kNodeCustomElementShift, + kUndefined = 3 << kNodeCustomElementShift, + kFailed = 4 << kNodeCustomElementShift, +}; + +enum class CloneChildrenFlag { kSkip, kClone, kCloneWithShadows }; + +// A Node is a base class for all objects in the DOM tree. +// The spec governing this interface can be found here: +// https://dom.spec.whatwg.org/#interface-node +class Node : public EventTarget { + DEFINE_WRAPPERTYPEINFO(); + friend class TreeScope; + + public: + enum NodeType { + kElementNode = 1, + kAttributeNode = 2, + kTextNode = 3, + kCommentNode = 8, + kDocumentNode = 9, + kDocumentTypeNode = 10, + kDocumentFragmentNode = 11, + }; + + using ImplType = Node*; + static Node* Create(ExecutingContext* context, ExceptionState& exception_state); + + // DOM methods & attributes for Node + virtual std::string nodeName() const = 0; + virtual std::string nodeValue() const = 0; + virtual void setNodeValue(const AtomicString&, ExceptionState&); + virtual NodeType nodeType() const = 0; + + [[nodiscard]] ContainerNode* parentNode() const; + [[nodiscard]] Element* parentElement() const; + [[nodiscard]] Node* previousSibling() const { return previous_.Get(); } + [[nodiscard]] Node* nextSibling() const { return next_.Get(); } + NodeList* childNodes(); + [[nodiscard]] Node* firstChild() const; + [[nodiscard]] Node* lastChild() const; + [[nodiscard]] Node& TreeRoot() const; + void remove(ExceptionState&); + + Node* insertBefore(Node* new_child, Node* ref_child, ExceptionState&); + Node* replaceChild(Node* new_child, Node* old_child, ExceptionState&); + Node* removeChild(Node* child, ExceptionState&); + Node* appendChild(Node* new_child, ExceptionState&); + + bool hasChildren() const { return firstChild(); } + Node* cloneNode(bool deep, ExceptionState&) const; + Node* cloneNode(ExceptionState&) const; + + // https://dom.spec.whatwg.org/#concept-node-clone + virtual Node* Clone(Document&, CloneChildrenFlag) const = 0; + + bool isEqualNode(Node*, ExceptionState& exception_state) const; + bool isEqualNode(Node*) const; + bool isSameNode(const Node* other, ExceptionState& exception_state) const { return this == other; } + + [[nodiscard]] AtomicString textContent(bool convert_brs_to_newlines = false) const; + virtual void setTextContent(const AtomicString&, ExceptionState& exception_state); + + // Other methods (not part of DOM) + [[nodiscard]] FORCE_INLINE bool IsTextNode() const { return GetDOMNodeType() == DOMNodeType::kText; } + [[nodiscard]] FORCE_INLINE bool IsContainerNode() const { return GetFlag(kIsContainerFlag); } + [[nodiscard]] FORCE_INLINE bool IsElementNode() const { return GetDOMNodeType() == DOMNodeType::kElement; } + [[nodiscard]] FORCE_INLINE bool IsDocumentFragment() const { + return GetDOMNodeType() == DOMNodeType::kDocumentFragment; + } + + [[nodiscard]] FORCE_INLINE bool IsHTMLElement() const { + return GetElementNamespaceType() == ElementNamespaceType::kHTML; + } + [[nodiscard]] FORCE_INLINE bool IsMathMLElement() const { + return GetElementNamespaceType() == ElementNamespaceType::kMathML; + } + [[nodiscard]] FORCE_INLINE bool IsSVGElement() const { + return GetElementNamespaceType() == ElementNamespaceType::kSVG; + } + + [[nodiscard]] CustomElementState GetCustomElementState() const { + return static_cast<CustomElementState>(node_flags_ & kCustomElementStateMask); + } + bool IsCustomElement() const { return GetCustomElementState() != CustomElementState::kUncustomized; } + void SetCustomElementState(CustomElementState); + + [[nodiscard]] virtual bool IsMediaElement() const { return false; } + [[nodiscard]] virtual bool IsAttributeNode() const { return false; } + [[nodiscard]] virtual bool IsCharacterDataNode() const { return false; } + + // StyledElements allow inline style (style="border: 1px"), presentational + // attributes (ex. color), class names (ex. class="foo bar") and other + // non-basic styling features. They also control if this element can + // participate in style sharing. + [[nodiscard]] bool IsStyledElement() const { return IsHTMLElement() || IsSVGElement() || IsMathMLElement(); } + + [[nodiscard]] bool IsDocumentNode() const; + + // Node's parent, shadow tree host. + [[nodiscard]] ContainerNode* ParentOrShadowHostNode() const; + [[nodiscard]] Element* ParentOrShadowHostElement() const; + void SetParentOrShadowHostNode(ContainerNode*); + + // --------------------------------------------------------------------------- + // Notification of document structure changes (see container_node.h for more + // notification methods) + // + // InsertedInto() implementations must not modify the DOM tree, and must not + // dispatch synchronous events. + virtual void InsertedInto(ContainerNode& insertion_point); + + // Notifies the node that it is no longer part of the tree. + // + // This is a dual of InsertedInto(), but does not require the overhead of + // event dispatching, and is called _after_ the node is removed from the tree. + // + // RemovedFrom() implementations must not modify the DOM tree, and must not + // dispatch synchronous events. + virtual void RemovedFrom(ContainerNode& insertion_point); + + // Returns the parent node, but nullptr if the parent node is a ShadowRoot. + [[nodiscard]] ContainerNode* NonShadowBoundaryParentNode() const; + + // These low-level calls give the caller responsibility for maintaining the + // integrity of the tree. + void SetPreviousSibling(Node* previous) { previous_ = previous; } + void SetNextSibling(Node* next) { next_ = next; } + + [[nodiscard]] bool HasEventTargetData() const { return GetFlag(kHasEventTargetDataFlag); } + void SetHasEventTargetData(bool flag) { SetFlag(flag, kHasEventTargetDataFlag); } + + [[nodiscard]] unsigned NodeIndex() const; + + // Returns the DOM ownerDocument attribute. This method never returns null, + // except in the case of a Document node. + [[nodiscard]] Document* ownerDocument() const; + + // Returns the document associated with this node. A Document node returns + // itself. + [[nodiscard]] Document& GetDocument() const { return GetTreeScope().GetDocument(); } + + [[nodiscard]] TreeScope& GetTreeScope() const { + assert(tree_scope_); + return *tree_scope_; + }; + + // Returns true if this node is connected to a document, false otherwise. + // See https://dom.spec.whatwg.org/#connected for the definition. + [[nodiscard]] bool isConnected() const { return GetFlag(kIsConnectedFlag); } + + [[nodiscard]] bool IsInDocumentTree() const { return isConnected(); } + [[nodiscard]] bool IsInTreeScope() const { return GetFlag(static_cast<NodeFlags>(kIsConnectedFlag)); } + + [[nodiscard]] bool IsDocumentTypeNode() const { return nodeType() == kDocumentTypeNode; } + [[nodiscard]] virtual bool ChildTypeAllowed(NodeType) const { return false; } + [[nodiscard]] unsigned CountChildren() const; + + bool IsDescendantOf(const Node*) const; + bool contains(const Node*, ExceptionState&) const; + [[nodiscard]] bool ContainsIncludingHostElements(const Node&) const; + Node* CommonAncestor(const Node&, ContainerNode* (*parent)(const Node&)) const; + + enum ShadowTreesTreatment { kTreatShadowTreesAsDisconnected, kTreatShadowTreesAsComposed }; + + EventTargetData* GetEventTargetData() override; + EventTargetData& EnsureEventTargetData() override; + + [[nodiscard]] bool IsFinishedParsingChildren() const { return GetFlag(kIsFinishedParsingChildrenFlag); } + + void SetHasDuplicateAttributes() { SetFlag(kHasDuplicateAttributes); } + [[nodiscard]] bool HasDuplicateAttribute() const { return GetFlag(kHasDuplicateAttributes); } + + [[nodiscard]] bool SelfOrAncestorHasDirAutoAttribute() const { return GetFlag(kSelfOrAncestorHasDirAutoAttribute); } + void SetSelfOrAncestorHasDirAutoAttribute() { SetFlag(kSelfOrAncestorHasDirAutoAttribute); } + void ClearSelfOrAncestorHasDirAutoAttribute() { ClearFlag(kSelfOrAncestorHasDirAutoAttribute); } + + NodeData& CreateNodeData(); + [[nodiscard]] bool HasData() const { return GetFlag(kHasDataFlag); } + // |RareData| cannot be replaced or removed once assigned. + [[nodiscard]] NodeData* Data() const { return node_data_.get(); } + NodeData& EnsureNodeData(); + + void Trace(GCVisitor*) const override; + + private: + enum NodeFlags : uint32_t { + kHasDataFlag = 1, + + // Node type flags. These never change once created. + kIsContainerFlag = 1 << 1, + kDOMNodeTypeMask = 0x3 << kDOMNodeTypeShift, + kElementNamespaceTypeMask = 0x3 << kElementNamespaceTypeShift, + + // Tree state flags. These change when the element is added/removed + // from a DOM tree. + kIsConnectedFlag = 1 << 8, + + // Set by the parser when the children are done parsing. + kIsFinishedParsingChildrenFlag = 1 << 10, + + kCustomElementStateMask = 0x7 << kNodeCustomElementShift, + kHasNameOrIsEditingTextFlag = 1 << 20, + kHasEventTargetDataFlag = 1 << 21, + + kHasDuplicateAttributes = 1 << 24, + + kSelfOrAncestorHasDirAutoAttribute = 1 << 27, + kDefaultNodeFlags = kIsFinishedParsingChildrenFlag, + // 2 bits remaining. + }; + + [[nodiscard]] FORCE_INLINE bool GetFlag(NodeFlags mask) const { return node_flags_ & mask; } + void SetFlag(bool f, NodeFlags mask) { node_flags_ = (node_flags_ & ~mask) | (-(int32_t)f & mask); } + void SetFlag(NodeFlags mask) { node_flags_ |= mask; } + void ClearFlag(NodeFlags mask) { node_flags_ &= ~mask; } + + enum class DOMNodeType : uint32_t { + kElement = 0, + kText = 1 << kDOMNodeTypeShift, + kDocumentFragment = 2 << kDOMNodeTypeShift, + kOther = 3 << kDOMNodeTypeShift, + }; + + [[nodiscard]] FORCE_INLINE DOMNodeType GetDOMNodeType() const { + return static_cast<DOMNodeType>(node_flags_ & kDOMNodeTypeMask); + } + + enum class ElementNamespaceType : uint32_t { + kHTML = 0, + kMathML = 1 << kElementNamespaceTypeShift, + kSVG = 2 << kElementNamespaceTypeShift, + kOther = 3 << kElementNamespaceTypeShift, + }; + [[nodiscard]] FORCE_INLINE ElementNamespaceType GetElementNamespaceType() const { + return static_cast<ElementNamespaceType>(node_flags_ & kElementNamespaceTypeMask); + } + + protected: + enum ConstructionType { + kCreateOther = kDefaultNodeFlags | static_cast<NodeFlags>(DOMNodeType::kOther) | + static_cast<NodeFlags>(ElementNamespaceType::kOther), + kCreateText = kDefaultNodeFlags | static_cast<NodeFlags>(DOMNodeType::kText) | + static_cast<NodeFlags>(ElementNamespaceType::kOther), + kCreateContainer = kDefaultNodeFlags | kIsContainerFlag | static_cast<NodeFlags>(DOMNodeType::kOther) | + static_cast<NodeFlags>(ElementNamespaceType::kOther), + kCreateElement = kDefaultNodeFlags | kIsContainerFlag | static_cast<NodeFlags>(DOMNodeType::kElement) | + static_cast<NodeFlags>(ElementNamespaceType::kOther), + kCreateDocumentFragment = kDefaultNodeFlags | kIsContainerFlag | + static_cast<NodeFlags>(DOMNodeType::kDocumentFragment) | + static_cast<NodeFlags>(ElementNamespaceType::kOther), + kCreateHTMLElement = kDefaultNodeFlags | kIsContainerFlag | static_cast<NodeFlags>(DOMNodeType::kElement) | + static_cast<NodeFlags>(ElementNamespaceType::kHTML), + kCreateMathMLElement = kDefaultNodeFlags | kIsContainerFlag | static_cast<NodeFlags>(DOMNodeType::kElement) | + static_cast<NodeFlags>(ElementNamespaceType::kMathML), + kCreateSVGElement = kDefaultNodeFlags | kIsContainerFlag | static_cast<NodeFlags>(DOMNodeType::kElement) | + static_cast<NodeFlags>(ElementNamespaceType::kSVG), + kCreateDocument = kCreateContainer | kIsConnectedFlag, + }; + + void SetTreeScope(TreeScope* scope) { tree_scope_ = scope; } + + Node(ExecutingContext* context, TreeScope*, ConstructionType); + Node() = delete; + ~Node(); + + private: + uint32_t node_flags_; + Member<Node> parent_or_shadow_host_node_; + Member<Node> previous_; + Member<Node> next_; + TreeScope* tree_scope_; + std::unique_ptr<EventTargetDataObject> event_target_data_; + std::unique_ptr<NodeData> node_data_; +}; + +inline ContainerNode* Node::ParentOrShadowHostNode() const { + return reinterpret_cast<ContainerNode*>(parent_or_shadow_host_node_.Get()); +} + +inline void Node::SetParentOrShadowHostNode(ContainerNode* parent) { + parent_or_shadow_host_node_ = reinterpret_cast<Node*>(parent); +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_NODE_H diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc new file mode 100644 index 0000000000..6029a5a6d1 --- /dev/null +++ b/bridge/core/dom/node_data.cc @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "node_data.h" +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "child_node_list.h" +#include "container_node.h" +#include "empty_node_list.h" +#include "node_list.h" + +namespace kraken { + +ChildNodeList* NodeData::GetChildNodeList(ContainerNode& node) { + assert(!child_node_list_ || &node == child_node_list_->VirtualOwnerNode()); + return To<ChildNodeList>(child_node_list_.Get()); +} + +ChildNodeList* NodeData::EnsureChildNodeList(ContainerNode& node) { + if (child_node_list_) + return To<ChildNodeList>(child_node_list_.Get()); + auto* list = MakeGarbageCollected<ChildNodeList>(&node); + child_node_list_ = list; + return list; +} + +EmptyNodeList* NodeData::EnsureEmptyChildNodeList(Node& node) { + if (child_node_list_) + return To<EmptyNodeList>(child_node_list_.Get()); + auto* list = MakeGarbageCollected<EmptyNodeList>(&node); + child_node_list_ = list; + return list; +} + +void NodeData::Trace(GCVisitor* visitor) const { + visitor->Trace(child_node_list_->ToQuickJSUnsafe()); +} + +} // namespace kraken diff --git a/bridge/core/dom/node_data.h b/bridge/core/dom/node_data.h new file mode 100644 index 0000000000..a1a4864a2a --- /dev/null +++ b/bridge/core/dom/node_data.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_NODE_DATA_H_ +#define KRAKENBRIDGE_CORE_DOM_NODE_DATA_H_ + +#include <cinttypes> +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "bindings/qjs/cppgc/gc_visitor.h" + +namespace kraken { + +class ChildNodeList; +class EmptyNodeList; +class ContainerNode; +class NodeList; +class Node; + +class NodeData { + public: + enum class ClassType : uint8_t { + kNodeRareData, + kElementRareData, + }; + + ChildNodeList* GetChildNodeList(ContainerNode& node); + + ChildNodeList* EnsureChildNodeList(ContainerNode& node); + + EmptyNodeList* EnsureEmptyChildNodeList(Node& node); + + void Trace(GCVisitor* visitor) const; + + private: + Member<NodeList> child_node_list_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_NODE_DATA_H_ diff --git a/bridge/core/dom/node_list.d.ts b/bridge/core/dom/node_list.d.ts new file mode 100644 index 0000000000..7267a7a193 --- /dev/null +++ b/bridge/core/dom/node_list.d.ts @@ -0,0 +1,8 @@ +import {Node} from "./node"; + +export interface NodeList { + readonly length: int64; + item(index: number): Node; + readonly [index: number]: Node; + new(): void; +} diff --git a/bridge/core/dom/node_list.h b/bridge/core/dom/node_list.h new file mode 100644 index 0000000000..10133a6687 --- /dev/null +++ b/bridge/core/dom/node_list.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_NODE_LIST_H_ +#define KRAKENBRIDGE_CORE_DOM_NODE_LIST_H_ + +#include "bindings/qjs/script_wrappable.h" + +namespace kraken { + +class Node; +class ExceptionState; + +class NodeList : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = NodeList*; + + static NodeList* Create(ExecutingContext* context, ExceptionState& exception_state) { return nullptr; }; + + NodeList(JSContext* ctx) : ScriptWrappable(ctx){}; + ~NodeList() override = default; + + // DOM methods & attributes for NodeList + virtual unsigned length() const = 0; + virtual Node* item(unsigned index, ExceptionState& exception_state) const = 0; + + // Other methods (not part of DOM) + virtual bool IsEmptyNodeList() const { return false; } + virtual bool IsChildNodeList() const { return false; } + + virtual Node* VirtualOwnerNode() const { return nullptr; } + + void Trace(GCVisitor* visitor) const override{}; + + protected: +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_NODE_LIST_H_ diff --git a/bridge/bindings/qjs/dom/node_test.cc b/bridge/core/dom/node_test.cc similarity index 68% rename from bridge/bindings/qjs/dom/node_test.cc rename to bridge/core/dom/node_test.cc index 8244e74d20..cf4ce4c718 100644 --- a/bridge/bindings/qjs/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -2,10 +2,10 @@ * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ -#include "event_target.h" #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "page.h" + +using namespace kraken; TEST(Node, appendChild) { bool static errorCalled = false; @@ -15,11 +15,12 @@ TEST(Node, appendChild) { logCalled = true; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "document.body.appendChild(div);" - "console.log(document.body.firstChild === div, document.body.lastChild === div, div.parentNode === document.body);"; + "console.log(document.body.firstChild === div, document.body.lastChild === div, div.parentNode === " + "document.body);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -34,7 +35,8 @@ TEST(Node, childNodes) { logCalled = true; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); + MemberMutationScope scope{context}; const char* code = "let div1 = document.createElement('div');" "let div2 = document.createElement('div');" @@ -51,6 +53,23 @@ TEST(Node, childNodes) { EXPECT_EQ(logCalled, true); } +TEST(Node, textNodeHaveEmptyChildNodes) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let text = document.createTextNode('helloworld');" + "console.log(text.childNodes);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + TEST(Node, textContent) { bool static errorCalled = false; bool static logCalled = false; @@ -59,7 +78,7 @@ TEST(Node, textContent) { logCalled = true; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let text1 = document.createTextNode('1234');" "let text2 = document.createTextNode('helloworld');" @@ -81,7 +100,7 @@ TEST(Node, setTextContent) { logCalled = true; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "div.textContent = '1234';" @@ -100,7 +119,7 @@ TEST(Node, ensureDetached) { logCalled = true; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "document.body.appendChild(div);" @@ -117,12 +136,16 @@ TEST(Node, ensureDetached) { TEST(Node, replaceBody) { bool static errorCalled = false; bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); + // const char* code = "let newbody = document.createElement('body'); document.documentElement.replaceChild(newbody, + // document.body)"; const char* code = "document.body = document.createElement('body');"; bridge->evaluateScript(code, strlen(code), "vm://", 0); @@ -131,20 +154,21 @@ TEST(Node, replaceBody) { TEST(Node, cloneNode) { std::string code = R"( -const div = document.createElement('div'); -div.style.width = '100px'; -div.style.height = '100px'; -div.style.backgroundColor = 'yellow'; -let str = '1234'; -div.setAttribute('id', str); -document.body.appendChild(div); + const div = document.createElement('div'); + div.style.width = '100px'; + div.style.height = '100px'; + div.style.backgroundColor = 'yellow'; + let str = '1234'; + div.setAttribute('id', str); + document.body.appendChild(div); -const div2 = div.cloneNode(true); -document.body.appendChild(div2); + const div2 = div.cloneNode(true); + document.body.appendChild(div2); -div2.setAttribute('id', '456'); + div2.setAttribute('id', '456'); -console.log(div.style.width == div2.style.height, div.getAttribute('id') == '1234', div2.getAttribute('id') == '456'); + console.log(div.style.width == div2.style.height, div.getAttribute('id') == '1234', div2.getAttribute('id') == + '456'); )"; bool static errorCalled = false; @@ -157,7 +181,7 @@ console.log(div.style.width == div2.style.height, div.getAttribute('id') == '123 KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -166,32 +190,33 @@ console.log(div.style.width == div2.style.height, div.getAttribute('id') == '123 TEST(Node, nestedNode) { std::string code = R"( -const div = document.createElement('div'); -div.style.width = '100px'; -div.style.height = '100px'; -div.style.backgroundColor = 'green'; -div.setAttribute('id', '123'); -document.body.appendChild(div) - -const child = document.createElement('div'); -child.style.width = '10px'; -child.style.height = '10px'; -child.style.backgroundColor = 'blue'; -child.setAttribute('id', 'child123'); -div.appendChild(child); - -const child2 = document.createElement('div'); -child2.style.width = '10px'; -child2.style.height = '10px'; -child2.style.backgroundColor = 'yellow'; -child2.setAttribute('id', 'child123'); -div.appendChild(child2); - -const div2 = div.cloneNode(true); -document.body.appendChild(div2); - -console.log( - div2.firstChild.getAttribute('id') === 'child123', div2.firstChild.style.width === '10px', div2.firstChild.style.height === '10px' + const div = document.createElement('div'); + div.style.width = '100px'; + div.style.height = '100px'; + div.style.backgroundColor = 'green'; + div.setAttribute('id', '123'); + document.body.appendChild(div) + + const child = document.createElement('div'); + child.style.width = '10px'; + child.style.height = '10px'; + child.style.backgroundColor = 'blue'; + child.setAttribute('id', 'child123'); + div.appendChild(child); + + const child2 = document.createElement('div'); + child2.style.width = '10px'; + child2.style.height = '10px'; + child2.style.backgroundColor = 'yellow'; + child2.setAttribute('id', 'child123'); + div.appendChild(child2); + + const div2 = div.cloneNode(true); + document.body.appendChild(div2); + + console.log( + div2.firstChild.getAttribute('id') === 'child123', div2.firstChild.style.width === '10px', + div2.firstChild.style.height === '10px' ); )"; @@ -205,7 +230,7 @@ console.log( KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(errorCalled, false); diff --git a/bridge/core/dom/node_traversal.cc b/bridge/core/dom/node_traversal.cc new file mode 100644 index 0000000000..4e4956d6f0 --- /dev/null +++ b/bridge/core/dom/node_traversal.cc @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "node_traversal.h" + +namespace kraken { + +Node* NodeTraversal::NextAncestorSibling(const Node& current) { + assert(!current.nextSibling()); + for (Node& parent : AncestorsOf(current)) { + if (parent.nextSibling()) + return parent.nextSibling(); + } + return nullptr; +} + +Node* NodeTraversal::NextAncestorSibling(const Node& current, const Node* stay_within) { + assert(!current.nextSibling()); + assert(¤t != stay_within); + for (Node& parent : AncestorsOf(current)) { + if (&parent == stay_within) + return nullptr; + if (parent.nextSibling()) + return parent.nextSibling(); + } + return nullptr; +} + +Node* NodeTraversal::LastWithin(const ContainerNode& current) { + Node* descendant = current.lastChild(); + for (Node* child = descendant; child; child = child->lastChild()) + descendant = child; + return descendant; +} + +Node& NodeTraversal::LastWithinOrSelf(Node& current) { + auto* curr_node = DynamicTo<ContainerNode>(current); + Node* last_descendant = curr_node ? NodeTraversal::LastWithin(*curr_node) : nullptr; + return last_descendant ? *last_descendant : current; +} + +Node* NodeTraversal::Previous(const Node& current, const Node* stay_within) { + if (¤t == stay_within) + return nullptr; + if (current.previousSibling()) { + Node* previous = current.previousSibling(); + while (Node* child = previous->lastChild()) + previous = child; + return previous; + } + return current.parentNode(); +} + +Node* NodeTraversal::PreviousAbsoluteSibling(const Node& current, const Node* stay_within) { + for (Node& node : InclusiveAncestorsOf(current)) { + if (&node == stay_within) + return nullptr; + if (Node* prev = node.previousSibling()) + return prev; + } + return nullptr; +} + +Node* NodeTraversal::NextPostOrder(const Node& current, const Node* stay_within) { + if (¤t == stay_within) + return nullptr; + if (!current.nextSibling()) + return current.parentNode(); + Node* next = current.nextSibling(); + while (Node* child = next->firstChild()) + next = child; + return next; +} + +Node* NodeTraversal::PreviousAncestorSiblingPostOrder(const Node& current, const Node* stay_within) { + assert(!current.previousSibling()); + for (Node& parent : NodeTraversal::AncestorsOf(current)) { + if (&parent == stay_within) + return nullptr; + if (parent.previousSibling()) + return parent.previousSibling(); + } + return nullptr; +} + +Node* NodeTraversal::PreviousPostOrder(const Node& current, const Node* stay_within) { + if (Node* last_child = current.lastChild()) + return last_child; + if (¤t == stay_within) + return nullptr; + if (current.previousSibling()) + return current.previousSibling(); + return PreviousAncestorSiblingPostOrder(current, stay_within); +} + +} // namespace kraken diff --git a/bridge/core/dom/node_traversal.h b/bridge/core/dom/node_traversal.h new file mode 100644 index 0000000000..8f166e3f35 --- /dev/null +++ b/bridge/core/dom/node_traversal.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ +#define KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ + +#include "container_node.h" +#include "foundation/macros.h" +#include "node.h" +#include "traversal_range.h" + +namespace kraken { + +class NodeTraversal { + KRAKEN_STATIC_ONLY(NodeTraversal); + + public: + using TraversalNodeType = Node; + + // Does a pre-order traversal of the tree to find the next node after this + // one. This uses the same order that tags appear in the source file. If the + // stayWithin argument is non-null, the traversal will stop once the specified + // node is reached. This can be used to restrict traversal to a particular + // sub-tree. + static Node* Next(const Node& current) { return TraverseNextTemplate(current); } + static Node* Next(const ContainerNode& current) { return TraverseNextTemplate(current); } + static Node* Next(const Node& current, const Node* stay_within) { return TraverseNextTemplate(current, stay_within); } + static Node* Next(const ContainerNode& current, const Node* stay_within) { + return TraverseNextTemplate(current, stay_within); + } + + // Like next, but skips children and starts with the next sibling. + static Node* NextSkippingChildren(const Node&); + static Node* NextSkippingChildren(const Node&, const Node* stay_within); + + static Node* FirstWithin(const Node& current) { return current.firstChild(); } + + static Node* LastWithin(const ContainerNode&); + static Node& LastWithinOrSelf(Node&); + + // Does a reverse pre-order traversal to find the node that comes before the + // current one in document order + static Node* Previous(const Node&, const Node* stay_within = nullptr); + + // Returns the previous direct sibling of the node, if there is one. If not, + // it will traverse up the ancestor chain until it finds an ancestor + // that has a previous sibling, returning that sibling. Or nullptr if none. + // See comment for |FlatTreeTraversal::PreviousAbsoluteSibling| for details. + static Node* PreviousAbsoluteSibling(const Node&, const Node* stay_within = nullptr); + + // Like next, but visits parents after their children. + static Node* NextPostOrder(const Node&, const Node* stay_within = nullptr); + + // Like previous, but visits parents before their children. + static Node* PreviousPostOrder(const Node&, const Node* stay_within = nullptr); + + static Node* NextAncestorSibling(const Node&); + static Node* NextAncestorSibling(const Node&, const Node* stay_within); + static Node& HighestAncestorOrSelf(const Node&); + + // Children traversal. + static Node* ChildAt(const Node& parent, unsigned index) { return ChildAtTemplate(parent, index); } + static Node* ChildAt(const ContainerNode& parent, unsigned index) { return ChildAtTemplate(parent, index); } + + // These functions are provided for matching with |FlatTreeTraversal|. + static bool HasChildren(const Node& parent) { return FirstChild(parent); } + static bool IsDescendantOf(const Node& node, const Node& other) { return node.IsDescendantOf(&other); } + static Node* FirstChild(const Node& parent) { return parent.firstChild(); } + static Node* LastChild(const Node& parent) { return parent.lastChild(); } + static Node* NextSibling(const Node& node) { return node.nextSibling(); } + static Node* PreviousSibling(const Node& node) { return node.previousSibling(); } + static ContainerNode* Parent(const Node& node) { return node.parentNode(); } + static unsigned Index(const Node& node) { return node.NodeIndex(); } + static unsigned CountChildren(const Node& parent) { return parent.CountChildren(); } + static ContainerNode* ParentOrShadowHostNode(const Node& node) { return node.ParentOrShadowHostNode(); } + + static TraversalAncestorRange<NodeTraversal> AncestorsOf(const Node&); + static TraversalAncestorRange<NodeTraversal> InclusiveAncestorsOf(const Node&); + static TraversalSiblingRange<NodeTraversal> ChildrenOf(const Node&); + static TraversalDescendantRange<NodeTraversal> DescendantsOf(const Node&); + static TraversalInclusiveDescendantRange<NodeTraversal> InclusiveDescendantsOf(const Node&); + static TraversalNextRange<NodeTraversal> StartsAt(const Node&); + static TraversalNextRange<NodeTraversal> StartsAfter(const Node&); + + private: + template <class NodeType> + static Node* TraverseNextTemplate(NodeType&); + template <class NodeType> + static Node* TraverseNextTemplate(NodeType&, const Node* stay_within); + template <class NodeType> + static Node* ChildAtTemplate(NodeType&, unsigned); + static Node* PreviousAncestorSiblingPostOrder(const Node& current, const Node* stay_within); +}; + +inline TraversalAncestorRange<NodeTraversal> NodeTraversal::AncestorsOf(const Node& node) { + return TraversalAncestorRange<NodeTraversal>(NodeTraversal::Parent(node)); +} + +inline TraversalAncestorRange<NodeTraversal> NodeTraversal::InclusiveAncestorsOf(const Node& node) { + return TraversalAncestorRange<NodeTraversal>(&node); +} + +inline TraversalSiblingRange<NodeTraversal> NodeTraversal::ChildrenOf(const Node& parent) { + return TraversalSiblingRange<NodeTraversal>(NodeTraversal::FirstChild(parent)); +} + +inline TraversalDescendantRange<NodeTraversal> NodeTraversal::DescendantsOf(const Node& root) { + return TraversalDescendantRange<NodeTraversal>(&root); +} + +inline TraversalInclusiveDescendantRange<NodeTraversal> NodeTraversal::InclusiveDescendantsOf(const Node& root) { + return TraversalInclusiveDescendantRange<NodeTraversal>(&root); +} + +inline TraversalNextRange<NodeTraversal> NodeTraversal::StartsAt(const Node& start) { + return TraversalNextRange<NodeTraversal>(&start); +} + +inline TraversalNextRange<NodeTraversal> NodeTraversal::StartsAfter(const Node& start) { + return TraversalNextRange<NodeTraversal>(NodeTraversal::Next(start)); +} + +template <class NodeType> +inline Node* NodeTraversal::TraverseNextTemplate(NodeType& current) { + if (current.hasChildren()) + return current.firstChild(); + if (current.nextSibling()) + return current.nextSibling(); + return NextAncestorSibling(current); +} + +template <class NodeType> +inline Node* NodeTraversal::TraverseNextTemplate(NodeType& current, const Node* stay_within) { + if (current.hasChildren()) + return current.firstChild(); + if (¤t == stay_within) + return nullptr; + if (current.nextSibling()) + return current.nextSibling(); + return NextAncestorSibling(current, stay_within); +} + +inline Node* NodeTraversal::NextSkippingChildren(const Node& current) { + if (current.nextSibling()) + return current.nextSibling(); + return NextAncestorSibling(current); +} + +inline Node* NodeTraversal::NextSkippingChildren(const Node& current, const Node* stay_within) { + if (¤t == stay_within) + return nullptr; + if (current.nextSibling()) + return current.nextSibling(); + return NextAncestorSibling(current, stay_within); +} + +inline Node& NodeTraversal::HighestAncestorOrSelf(const Node& current) { + Node* highest = const_cast<Node*>(¤t); + while (highest->parentNode()) + highest = highest->parentNode(); + return *highest; +} + +template <class NodeType> +inline Node* NodeTraversal::ChildAtTemplate(NodeType& parent, unsigned index) { + Node* child = parent.firstChild(); + while (child && index--) + child = child->nextSibling(); + return child; +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc new file mode 100644 index 0000000000..16dcbafcca --- /dev/null +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "scripted_animation_controller.h" +#include "frame_request_callback_collection.h" + +namespace kraken { + +static void handleRAFTransientCallback(void* ptr, int32_t contextId, double highResTimeStamp, const char* errmsg) { + auto* frame_callback = static_cast<FrameCallback*>(ptr); + auto* context = frame_callback->context(); + + if (!context->IsValid()) + return; + + if (errmsg != nullptr) { + JSValue exception = JS_ThrowTypeError(frame_callback->context()->ctx(), "%s", errmsg); + context->HandleException(&exception); + return; + } + + // Trigger callbacks. + frame_callback->Fire(highResTimeStamp); +} + +uint32_t ScriptAnimationController::RegisterFrameCallback(const std::shared_ptr<FrameCallback>& frame_callback, + ExceptionState& exception_state) { + auto* context = frame_callback->context(); + + if (context->dartMethodPtr()->requestAnimationFrame == nullptr) { + exception_state.ThrowException( + context->ctx(), ErrorType::InternalError, + "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) is not registered."); + return -1; + } + + uint32_t requestId = context->dartMethodPtr()->requestAnimationFrame(frame_callback.get(), context->contextId(), + handleRAFTransientCallback); + + // Register frame callback to collection. + frame_request_callback_collection_.RegisterFrameCallback(requestId, frame_callback); + + return requestId; +} + +void ScriptAnimationController::CancelFrameCallback(ExecutingContext* context, + uint32_t callbackId, + ExceptionState& exception_state) { + if (context->dartMethodPtr()->cancelAnimationFrame == nullptr) { + exception_state.ThrowException( + context->ctx(), ErrorType::InternalError, + "Failed to execute 'cancelAnimationFrame': dart method (cancelAnimationFrame) is not registered."); + return; + } + + context->dartMethodPtr()->cancelAnimationFrame(context->contextId(), callbackId); + frame_request_callback_collection_.CancelFrameCallback(callbackId); +} + +void ScriptAnimationController::Trace(GCVisitor* visitor) const { + frame_request_callback_collection_.Trace(visitor); +} + +} // namespace kraken diff --git a/bridge/core/dom/scripted_animation_controller.h b/bridge/core/dom/scripted_animation_controller.h new file mode 100644 index 0000000000..75832c1767 --- /dev/null +++ b/bridge/core/dom/scripted_animation_controller.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ + +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "frame_request_callback_collection.h" + +namespace kraken { + +class ScriptAnimationController { + public: + // Animation frame callbacks are used for requestAnimationFrame(). + uint32_t RegisterFrameCallback(const std::shared_ptr<FrameCallback>& callback, ExceptionState& exception_state); + void CancelFrameCallback(ExecutingContext* context, uint32_t callbackId, ExceptionState& exception_state); + + void Trace(GCVisitor* visitor) const; + + private: + FrameRequestCallbackCollection frame_request_callback_collection_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ diff --git a/bridge/core/dom/scroll_options.d.ts b/bridge/core/dom/scroll_options.d.ts new file mode 100644 index 0000000000..9a9241ac9e --- /dev/null +++ b/bridge/core/dom/scroll_options.d.ts @@ -0,0 +1,7 @@ +// @ts-ignore +@Dictionary() +export interface ScrollOptions { + readonly behavior: string; +} + + diff --git a/bridge/core/dom/scroll_to_options.d.ts b/bridge/core/dom/scroll_to_options.d.ts new file mode 100644 index 0000000000..22d3d67918 --- /dev/null +++ b/bridge/core/dom/scroll_to_options.d.ts @@ -0,0 +1,11 @@ +// @ts-ignore +import {ScrollOptions} from "./scroll_options"; + +// @ts-ignore +@Dictionary() +export interface ScrollToOptions extends ScrollOptions { + readonly top: number; + readonly left: number; +} + + diff --git a/bridge/core/dom/text.cc b/bridge/core/dom/text.cc new file mode 100644 index 0000000000..31053d3796 --- /dev/null +++ b/bridge/core/dom/text.cc @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "text.h" +#include "document.h" + +namespace kraken { + +Text* Text::Create(Document& document, const AtomicString& value) { + return MakeGarbageCollected<Text>(document, value, ConstructionType::kCreateText); +} + +Text* Text::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected<Text>(*context->document(), AtomicString::Empty(context->ctx()), + ConstructionType::kCreateText); +} + +Text* Text::Create(ExecutingContext* context, const AtomicString& value, ExceptionState& executing_context) { + return MakeGarbageCollected<Text>(*context->document(), value, ConstructionType::kCreateText); +} + +Node::NodeType Text::nodeType() const { + return Node::kTextNode; +} + +std::string Text::nodeName() const { + return "#text"; +} + +Node* Text::Clone(Document& document, CloneChildrenFlag flag) const { + return Create(document, data()); +} + +} // namespace kraken diff --git a/bridge/core/dom/text.d.ts b/bridge/core/dom/text.d.ts new file mode 100644 index 0000000000..d8654f1baf --- /dev/null +++ b/bridge/core/dom/text.d.ts @@ -0,0 +1,5 @@ +import {CharacterData} from "./character_data"; + +interface Text extends CharacterData { + new(value?: string): Text; +} diff --git a/bridge/core/dom/text.h b/bridge/core/dom/text.h new file mode 100644 index 0000000000..3fd3a4dca1 --- /dev/null +++ b/bridge/core/dom/text.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_TEXT_H_ +#define KRAKENBRIDGE_CORE_DOM_TEXT_H_ + +#include "character_data.h" + +namespace kraken { + +class Text : public CharacterData { + DEFINE_WRAPPERTYPEINFO(); + + public: + static const unsigned kDefaultLengthLimit = 1 << 16; + + static Text* Create(Document&, const AtomicString&); + static Text* Create(ExecutingContext* context, ExceptionState& executing_context); + static Text* Create(ExecutingContext* context, const AtomicString& value, ExceptionState& executing_context); + + Text(TreeScope& tree_scope, const AtomicString& data, ConstructionType type) : CharacterData(tree_scope, data, type) { + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateTextNode, + std::move(data.ToNativeString()), (void*)bindingObject()); + } + + NodeType nodeType() const override; + + private: + std::string nodeName() const override; + Node* Clone(Document&, CloneChildrenFlag) const override; +}; + +template <> +struct DowncastTraits<Text> { + static bool AllowFrom(const Node& node) { return node.IsTextNode(); }; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_TEXT_H_ diff --git a/bridge/core/dom/traversal_range.h b/bridge/core/dom/traversal_range.h new file mode 100644 index 0000000000..727e8b266c --- /dev/null +++ b/bridge/core/dom/traversal_range.h @@ -0,0 +1,123 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DOM_TRAVERSAL_RANGE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_TRAVERSAL_RANGE_H_ + +#include "foundation/macros.h" + +namespace kraken { + +class Node; + +template <class Iterator> +class TraversalRange { + KRAKEN_STATIC_ONLY(TraversalRange); + + public: + using StartNodeType = typename Iterator::StartNodeType; + explicit TraversalRange(const StartNodeType* start) : start_(start) {} + Iterator begin() { return Iterator(start_); } + Iterator end() { return Iterator::End(); } + + private: + const StartNodeType* start_; +}; + +template <class Traversal> +class TraversalIteratorBase { + KRAKEN_STATIC_ONLY(TraversalIteratorBase); + + public: + using NodeType = typename Traversal::TraversalNodeType; + NodeType& operator*() { return *current_; } + bool operator!=(const TraversalIteratorBase& rval) const { return current_ != rval.current_; } + + protected: + explicit TraversalIteratorBase(NodeType* current) : current_(current) {} + + NodeType* current_; +}; + +template <class Traversal> +class TraversalIterator : public TraversalIteratorBase<Traversal> { + public: + using StartNodeType = typename Traversal::TraversalNodeType; + using TraversalIteratorBase<Traversal>::current_; + + explicit TraversalIterator(const StartNodeType* start) + : TraversalIteratorBase<Traversal>(const_cast<StartNodeType*>(start)) {} + + void operator++() { current_ = Traversal::Next(*current_); } + + static TraversalIterator End() { return TraversalIterator(); } + + private: + TraversalIterator() : TraversalIteratorBase<Traversal>(nullptr) {} +}; + +template <class Traversal> +class TraversalDescendantIterator : public TraversalIteratorBase<Traversal> { + public: + using StartNodeType = Node; + using TraversalIteratorBase<Traversal>::current_; + + explicit TraversalDescendantIterator(const StartNodeType* start) + : TraversalIteratorBase<Traversal>(start ? Traversal::FirstWithin(*start) : nullptr), root_(start) {} + + void operator++() { current_ = Traversal::Next(*current_, root_); } + static TraversalDescendantIterator End() { return TraversalDescendantIterator(); } + + private: + TraversalDescendantIterator() : TraversalIteratorBase<Traversal>(nullptr) {} + const StartNodeType* root_ = nullptr; +}; + +template <class Traversal> +class TraversalInclusiveDescendantIterator : public TraversalIteratorBase<Traversal> { + public: + using StartNodeType = typename Traversal::TraversalNodeType; + using TraversalIteratorBase<Traversal>::current_; + + explicit TraversalInclusiveDescendantIterator(const StartNodeType* start) + : TraversalIteratorBase<Traversal>(const_cast<StartNodeType*>(start)), root_(start) {} + void operator++() { current_ = Traversal::Next(*current_, root_); } + static TraversalInclusiveDescendantIterator End() { return TraversalInclusiveDescendantIterator(nullptr); } + + private: + const StartNodeType* root_; +}; + +template <class Traversal> +class TraversalParent { + public: + using TraversalNodeType = typename Traversal::TraversalNodeType; + static TraversalNodeType* Next(const TraversalNodeType& node) { return Traversal::Parent(node); } +}; + +template <class Traversal> +class TraversalSibling { + public: + using TraversalNodeType = typename Traversal::TraversalNodeType; + static TraversalNodeType* Next(const TraversalNodeType& node) { return Traversal::NextSibling(node); } +}; + +template <class T> +using TraversalNextRange = TraversalRange<TraversalIterator<T>>; + +template <class T> +using TraversalAncestorRange = TraversalRange<TraversalIterator<TraversalParent<T>>>; + +template <class T> +using TraversalSiblingRange = TraversalRange<TraversalIterator<TraversalSibling<T>>>; + +template <class T> +using TraversalDescendantRange = TraversalRange<TraversalDescendantIterator<T>>; + +template <class T> +using TraversalInclusiveDescendantRange = TraversalRange<TraversalInclusiveDescendantIterator<T>>; + +} // namespace kraken + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_DOM_TRAVERSAL_RANGE_H_ diff --git a/bridge/core/dom/tree_scope.cc b/bridge/core/dom/tree_scope.cc new file mode 100644 index 0000000000..3c12ba9362 --- /dev/null +++ b/bridge/core/dom/tree_scope.cc @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "tree_scope.h" +#include "document.h" + +namespace kraken { + +TreeScope::TreeScope(Document& document) : root_node_(&document), document_(&document) { + root_node_->SetTreeScope(this); +} + +} // namespace kraken diff --git a/bridge/core/dom/tree_scope.h b/bridge/core/dom/tree_scope.h new file mode 100644 index 0000000000..59580fb592 --- /dev/null +++ b/bridge/core/dom/tree_scope.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ +#define KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ + +#include <cassert> + +namespace kraken { + +class ContainerNode; +class Document; + +// The root node of a document tree (in which case this is a Document) or of a +// shadow tree (in which case this is a ShadowRoot). Various things, like +// element IDs, are scoped to the TreeScope in which they are rooted, if any. +// +// A class which inherits both Node and TreeScope must call clearRareData() in +// its destructor so that the Node destructor no longer does problematic +// NodeList cache manipulation in the destructor. +class TreeScope { + friend class Node; + + public: + Document& GetDocument() const { + assert(document_); + return *document_; + } + + protected: + explicit TreeScope(Document&); + + private: + ContainerNode* root_node_; + Document* document_; + TreeScope* parent_tree_scope_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ diff --git a/bridge/core/events/close_event.cc b/bridge/core/events/close_event.cc new file mode 100644 index 0000000000..ddf72d20d0 --- /dev/null +++ b/bridge/core/events/close_event.cc @@ -0,0 +1,5 @@ +// +// Created by andycall on 2022/1/29. +// + +#include "close_event.h" diff --git a/bridge/bindings/qjs/dom/events/close_event.d.ts b/bridge/core/events/close_event.d.ts similarity index 74% rename from bridge/bindings/qjs/dom/events/close_event.d.ts rename to bridge/core/events/close_event.d.ts index 0e5ae36fb1..7b5a552c91 100644 --- a/bridge/bindings/qjs/dom/events/close_event.d.ts +++ b/bridge/core/events/close_event.d.ts @@ -1,6 +1,3 @@ -interface Event {} -type int64 = number; - interface CloseEvent extends Event { readonly code: int64; readonly reason: string; diff --git a/bridge/core/events/close_event.h b/bridge/core/events/close_event.h new file mode 100644 index 0000000000..d9cf927807 --- /dev/null +++ b/bridge/core/events/close_event.h @@ -0,0 +1,10 @@ +// +// Created by andycall on 2022/1/29. +// + +#ifndef KRAKENBRIDGE_CLOSE_EVENT_H +#define KRAKENBRIDGE_CLOSE_EVENT_H + +class close_event {}; + +#endif // KRAKENBRIDGE_CLOSE_EVENT_H diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc new file mode 100644 index 0000000000..f15d37f2c9 --- /dev/null +++ b/bridge/core/events/error_event.cc @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "error_event.h" + +namespace kraken { + +ErrorEvent* ErrorEvent::Create(ExecutingContext* context, const std::string& message) { + return MakeGarbageCollected<ErrorEvent>(context, message); +} +ErrorEvent* ErrorEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { + return MakeGarbageCollected<ErrorEvent>(context, type, exception_state); +} +ErrorEvent* ErrorEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<ErrorEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<ErrorEvent>(context, type, initializer, exception_state); +} + +ErrorEvent::ErrorEvent(ExecutingContext* context, const std::string& message) + : Event(context), message_(message), source_location_(std::make_unique<SourceLocation>("", 0, 0)) {} + +ErrorEvent::ErrorEvent(ExecutingContext* context, const std::string& message, std::unique_ptr<SourceLocation> location) + : Event(context), message_(message), source_location_(std::move(location)) {} + +ErrorEvent::ErrorEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : Event(context), message_(type.ToStdString()), source_location_(std::make_unique<SourceLocation>("", 0, 0)) {} + +ErrorEvent::ErrorEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<ErrorEventInit>& initializer, + ExceptionState& exception_state) + : Event(context), + message_(type.ToStdString()), + error_(initializer->error()), + source_location_(std::make_unique<SourceLocation>(initializer->filename().ToStdString(), + initializer->lineno(), + initializer->colno())) {} + +} // namespace kraken diff --git a/bridge/core/events/error_event.d.ts b/bridge/core/events/error_event.d.ts new file mode 100644 index 0000000000..f04dc51934 --- /dev/null +++ b/bridge/core/events/error_event.d.ts @@ -0,0 +1,11 @@ +import {ErrorEventInit} from "./error_event_init"; +import {Event} from "../dom/events/event"; + +interface ErrorEvent extends Event { + readonly message: string; + readonly filename: string; + readonly lineno: number; + readonly colno: number; + readonly error: any; + new(eventType: string, init?: ErrorEventInit) : ErrorEvent; +} diff --git a/bridge/core/events/error_event.h b/bridge/core/events/error_event.h new file mode 100644 index 0000000000..0f49ef5149 --- /dev/null +++ b/bridge/core/events/error_event.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ +#define KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ + +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/source_location.h" +#include "core/dom/events/event.h" +#include "qjs_error_event_init.h" + +namespace kraken { + +class ErrorEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = ErrorEvent*; + static ErrorEvent* Create(ExecutingContext* context, const std::string& message); + static ErrorEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + static ErrorEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<ErrorEventInit>& initializer, + ExceptionState& exception_state); + + explicit ErrorEvent(ExecutingContext* context, const std::string& message); + explicit ErrorEvent(ExecutingContext* context, const std::string& message, std::unique_ptr<SourceLocation> location); + explicit ErrorEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + explicit ErrorEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<ErrorEventInit>& initializer, + ExceptionState& exception_state); + + // As |message| is exposed to JavaScript, never return |unsanitized_message_|. + const std::string& message() const { return message_; } + const std::string& filename() const { return source_location_->Url(); } + unsigned lineno() const { return source_location_->LineNumber(); } + unsigned colno() const { return source_location_->ColumnNumber(); } + + ScriptValue error() const { return error_; } + + SourceLocation* Location() const { return source_location_.get(); } + + private: + std::string message_; + std::unique_ptr<SourceLocation> source_location_{nullptr}; + ScriptValue error_; +}; + +template <> +struct DowncastTraits<ErrorEvent> { + static bool AllowFrom(const Event& event) { return event.IsErrorEvent(); } +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ diff --git a/bridge/core/events/error_event_init.d.ts b/bridge/core/events/error_event_init.d.ts new file mode 100644 index 0000000000..9078a121a3 --- /dev/null +++ b/bridge/core/events/error_event_init.d.ts @@ -0,0 +1,11 @@ +import { EventInit } from "../dom/events/event_init"; + +// @ts-ignore +@Dictionary() +export interface ErrorEventInit extends EventInit { + message?: string; + filename?: string; + lineno: int64; + colno: int64; + error: any; +} diff --git a/bridge/core/events/event_type_names.json5 b/bridge/core/events/event_type_names.json5 new file mode 100644 index 0000000000..6b1d1756da --- /dev/null +++ b/bridge/core/events/event_type_names.json5 @@ -0,0 +1,221 @@ +{ + "annotation": "Simplified from https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/events/event_type_names.json5", + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "event_type_names" + } + ] + }, + "data": [ + "DOMActivate", + "DOMCharacterDataModified", + "DOMContentLoaded", + "DOMFocusIn", + "DOMFocusOut", + "DOMNodeInserted", + "DOMNodeInsertedIntoDocument", + "DOMNodeRemoved", + "DOMNodeRemovedFromDocument", + "DOMSubtreeModified", + "abort", + "abortpayment", + "activate", + "active", + "addsourcebuffer", + "addtrack", + "animationcancel", + "animationend", + "animationiteration", + "animationstart", + "backgroundfetchabort", + "backgroundfetchclick", + "backgroundfetchfail", + "backgroundfetchsuccess", + "beforeunload", + "beginEvent", + "blocked", + "blur", + "boundary", + "cached", + "cancel", + "canplay", + "canplaythrough", + "capturehandlechange", + "change", + "checking", + "click", + "close", + "closing", + "complete", + "compositionend", + "compositionstart", + "compositionupdate", + "connect", + "contextlost", + "contextmenu", + "contextrestored", + "controllerchange", + "cookiechange", + "copy", + "contentdelete", + "crossoriginmessage", + "currentscreenchange", + "cuechange", + "currententrychange", + "cut", + "datachannel", + "dblclick", + "defaultsessionstart", + "disconnect", + "display", + "drop", + "durationchange", + "emptied", + "encrypted", + "end", + "ended", + "endEvent", + "enter", + "error", + "exit", + "fetch", + "finish", + "focus", + "focusin", + "focusout", + "freeze", + "fullscreenchange", + "fullscreenerror", + "hashchange", + "hide", + "inactive", + "input", + "inputreport", + "inputsourceschange", + "install", + "interfacerequest", + "invalid", + "keydown", + "keypress", + "keystatuseschange", + "keyup", + "languagechange", + "leavepictureinpicture", + "levelchange", + "load", + "loadeddata", + "loadedmetadata", + "loadend", + "loading", + "loadstart", + "lostpointercapture", + "mark", + "message", + "messageerror", + "mousedown", + "mouseenter", + "mouseleave", + "mousemove", + "mouseout", + "mouseover", + "mouseup", + "mousewheel", + "mute", + "navigate", + "navigateerror", + "navigatesuccess", + "noupdate", + "open", + "orientationchange", + "overscroll", + "pagehide", + "pageshow", + "paste", + "pause", + "play", + "playing", + "pointercancel", + "pointerdown", + "pointerenter", + "pointerleave", + "pointerlockchange", + "pointerlockerror", + "pointermove", + "pointerout", + "pointerover", + "pointerup", + "popstate", + "progress", + "processorerror", + "push", + "pushsubscriptionchange", + "ratechange", + "reading", + "readingerror", + "readystatechange", + "reflectionchange", + "rejectionhandled", + "release", + "remove", + "removestream", + "removetrack", + "repeatEvent", + "reset", + "resize", + "result", + "resume", + "screenschange", + "scroll", + "scrollend", + "search", + "seeked", + "seeking", + "select", + "selectionchange", + "selectstart", + "show", + "squeeze", + "squeezeend", + "squeezestart", + "stalled", + "start", + "stop", + "statechange", + "storage", + "submit", + "success", + "suspend", + "sync", + "terminate", + "textInput", + "textupdate", + "textformatupdate", + "toggle", + "tonechange", + "touchcancel", + "touchend", + "touchmove", + "touchstart", + "transitioncancel", + "transitionend", + "transitionrun", + "transitionstart", + "typechange", + "uncapturederror", + "unhandledrejection", + "unload", + "unmute", + "update", + "versionchange", + "visibilitychange", + "waiting", + "waitingforkey", + "webglcontextcreationerror", + "webglcontextlost", + "webglcontextrestored", + "wheel", + "zoom" + ] +} diff --git a/bridge/bindings/qjs/dom/events/gesture_event.d.ts b/bridge/core/events/gesture_event.d.ts similarity index 93% rename from bridge/bindings/qjs/dom/events/gesture_event.d.ts rename to bridge/core/events/gesture_event.d.ts index be63cc1e58..063db06d4e 100644 --- a/bridge/bindings/qjs/dom/events/gesture_event.d.ts +++ b/bridge/core/events/gesture_event.d.ts @@ -1,5 +1,3 @@ -interface Event {} - interface GestureEvent extends Event { readonly state: string; readonly direction: string; diff --git a/bridge/core/events/input_event.cc b/bridge/core/events/input_event.cc new file mode 100644 index 0000000000..eb80d75711 --- /dev/null +++ b/bridge/core/events/input_event.cc @@ -0,0 +1,5 @@ +// +// Created by andycall on 2022/1/29. +// + +#include "input_event.h" diff --git a/bridge/bindings/qjs/dom/events/input_event.d.ts b/bridge/core/events/input_event.d.ts similarity index 68% rename from bridge/bindings/qjs/dom/events/input_event.d.ts rename to bridge/core/events/input_event.d.ts index e0693e9bd6..481679126b 100644 --- a/bridge/bindings/qjs/dom/events/input_event.d.ts +++ b/bridge/core/events/input_event.d.ts @@ -1,4 +1,4 @@ -interface Event {} +import {Event} from "../dom/events/event"; interface InputEvent extends Event { readonly inputType: string; diff --git a/bridge/core/events/input_event.h b/bridge/core/events/input_event.h new file mode 100644 index 0000000000..ccdeba49bc --- /dev/null +++ b/bridge/core/events/input_event.h @@ -0,0 +1,10 @@ +// +// Created by andycall on 2022/1/29. +// + +#ifndef KRAKENBRIDGE_INPUT_EVENT_H +#define KRAKENBRIDGE_INPUT_EVENT_H + +class input_event {}; + +#endif // KRAKENBRIDGE_INPUT_EVENT_H diff --git a/bridge/core/events/intersection_change_event.cc b/bridge/core/events/intersection_change_event.cc new file mode 100644 index 0000000000..b18d9998ac --- /dev/null +++ b/bridge/core/events/intersection_change_event.cc @@ -0,0 +1,5 @@ +// +// Created by andycall on 2022/1/29. +// + +#include "intersection_change_event.h" diff --git a/bridge/bindings/qjs/dom/events/intersection_change.d.ts b/bridge/core/events/intersection_change_event.d.ts similarity index 81% rename from bridge/bindings/qjs/dom/events/intersection_change.d.ts rename to bridge/core/events/intersection_change_event.d.ts index 0e42d5e1e2..f612ab02d7 100644 --- a/bridge/bindings/qjs/dom/events/intersection_change.d.ts +++ b/bridge/core/events/intersection_change_event.d.ts @@ -1,5 +1,3 @@ -interface Event {} - interface IntersectionChangeEvent extends Event { readonly intersectionRatio: number; } diff --git a/bridge/core/events/intersection_change_event.h b/bridge/core/events/intersection_change_event.h new file mode 100644 index 0000000000..40e732b1da --- /dev/null +++ b/bridge/core/events/intersection_change_event.h @@ -0,0 +1,10 @@ +// +// Created by andycall on 2022/1/29. +// + +#ifndef KRAKENBRIDGE_INTERSECTION_CHANGE_EVENT_H +#define KRAKENBRIDGE_INTERSECTION_CHANGE_EVENT_H + +class intersection_change_event {}; + +#endif // KRAKENBRIDGE_INTERSECTION_CHANGE_EVENT_H diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc new file mode 100644 index 0000000000..6f2323706a --- /dev/null +++ b/bridge/core/events/message_event.cc @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "message_event.h" + +namespace kraken { + +MessageEvent* MessageEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { + return MakeGarbageCollected<MessageEvent>(context, type); +} + +MessageEvent* MessageEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MessageEventInit>& init, + ExceptionState& exception_state) { + return MakeGarbageCollected<MessageEvent>(context, type, init); +} + +MessageEvent::MessageEvent(ExecutingContext* context, const AtomicString& type) : Event(context, type) {} + +MessageEvent::MessageEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MessageEventInit>& init) + : Event(context, type), + data_(init->data()), + origin_(init->origin()), + lastEventId_(init->lastEventId()), + source_(init->source()) {} + +ScriptValue MessageEvent::data() const { + return data_; +} + +AtomicString MessageEvent::origin() const { + return origin_; +} + +AtomicString MessageEvent::lastEventId() const { + return lastEventId_; +} + +AtomicString MessageEvent::source() const { + return source_; +} + +} // namespace kraken diff --git a/bridge/core/events/message_event.d.ts b/bridge/core/events/message_event.d.ts new file mode 100644 index 0000000000..9d9a161dbd --- /dev/null +++ b/bridge/core/events/message_event.d.ts @@ -0,0 +1,10 @@ +import {Event} from "../dom/events/event"; +import {MessageEventInit} from "./message_event_init"; + +export interface MessageEvent extends Event { + new(type: string, init?: MessageEventInit): MessageEvent; + readonly data: any; + readonly origin: string; + readonly lastEventId: string; + readonly source: string; +} diff --git a/bridge/core/events/message_event.h b/bridge/core/events/message_event.h new file mode 100644 index 0000000000..c6f17aa893 --- /dev/null +++ b/bridge/core/events/message_event.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ +#define KRAKENBRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ + +#include "core/dom/events/event.h" +#include "qjs_message_event_init.h" + +namespace kraken { + +class MessageEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = MessageEvent*; + + static MessageEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + static MessageEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MessageEventInit>& init, + ExceptionState& exception_state); + + explicit MessageEvent(ExecutingContext* context, const AtomicString& type); + explicit MessageEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MessageEventInit>& init); + + ScriptValue data() const; + AtomicString origin() const; + AtomicString lastEventId() const; + AtomicString source() const; + + private: + ScriptValue data_; + AtomicString origin_; + AtomicString lastEventId_; + AtomicString source_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ diff --git a/bridge/core/events/message_event_init.d.ts b/bridge/core/events/message_event_init.d.ts new file mode 100644 index 0000000000..480e3b6075 --- /dev/null +++ b/bridge/core/events/message_event_init.d.ts @@ -0,0 +1,11 @@ +import { EventInit } from "../dom/events/event_init"; + +// @ts-ignore +@Dictionary() +export interface MessageEventInit extends EventInit { + data: any; + origin: string; + lastEventId: string; + source: string; + // TODO: add ports property. +} diff --git a/bridge/bindings/qjs/dom/events/touch_event.cc b/bridge/core/events/touch_event.cc similarity index 96% rename from bridge/bindings/qjs/dom/events/touch_event.cc rename to bridge/core/events/touch_event.cc index 7e46737de1..d4da53525d 100644 --- a/bridge/bindings/qjs/dom/events/touch_event.cc +++ b/bridge/core/events/touch_event.cc @@ -3,17 +3,18 @@ */ #include "touch_event.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/qjs_engine_patch.h" #include "page.h" -namespace kraken::binding::qjs { +namespace kraken { void bindTouchEvent(ExecutionContext* context) { auto* constructor = TouchEvent::instance(context); context->defineGlobalProperty("TouchEvent", constructor->jsObject); } -TouchList::TouchList(ExecutionContext* context, NativeTouch** touches, int64_t length) : ExoticHostObject(context, "TouchList"), m_touches(touches), _length(length) {} +TouchList::TouchList(ExecutionContext* context, NativeTouch** touches, int64_t length) + : ExoticHostObject(context, "TouchList"), m_touches(touches), _length(length) {} JSValue TouchList::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { std::string key = jsAtomToStdString(ctx, atom); @@ -37,7 +38,8 @@ IMPL_PROPERTY_SETTER(TouchList, length)(JSContext* ctx, JSValue this_val, int ar return JS_NULL; } -Touch::Touch(ExecutionContext* context, NativeTouch* nativeTouch) : HostObject(context, "Touch"), m_nativeTouch(nativeTouch) {} +Touch::Touch(ExecutionContext* context, NativeTouch* nativeTouch) + : HostObject(context, "Touch"), m_nativeTouch(nativeTouch) {} IMPL_PROPERTY_GETTER(Touch, identifier)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* object = static_cast<Touch*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); @@ -225,7 +227,8 @@ IMPL_PROPERTY_GETTER(TouchEvent, targetTouches)(JSContext* ctx, JSValue this_val IMPL_PROPERTY_GETTER(TouchEvent, changedTouches)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* event = static_cast<TouchEventInstance*>(JS_GetOpaque(this_val, Event::kEventClassID)); auto* nativeEvent = reinterpret_cast<NativeTouchEvent*>(event->nativeEvent); - auto* changedTouchList = new TouchList(event->m_context, nativeEvent->changedTouches, nativeEvent->changedTouchesLength); + auto* changedTouchList = + new TouchList(event->m_context, nativeEvent->changedTouches, nativeEvent->changedTouchesLength); return changedTouchList->jsObject; } @@ -253,6 +256,7 @@ IMPL_PROPERTY_GETTER(TouchEvent, shiftKey)(JSContext* ctx, JSValue this_val, int return JS_NewBool(ctx, nativeEvent->shiftKey ? 1 : 0); } -TouchEventInstance::TouchEventInstance(TouchEvent* event, NativeEvent* nativeEvent) : EventInstance(event, nativeEvent) {} +TouchEventInstance::TouchEventInstance(TouchEvent* event, NativeEvent* nativeEvent) + : EventInstance(event, nativeEvent) {} -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/events/touch_event.h b/bridge/core/events/touch_event.h similarity index 97% rename from bridge/bindings/qjs/dom/events/touch_event.h rename to bridge/core/events/touch_event.h index 56f3c19196..d5b408a6a5 100644 --- a/bridge/bindings/qjs/dom/events/touch_event.h +++ b/bridge/core/events/touch_event.h @@ -7,7 +7,7 @@ #include "bindings/qjs/dom/element.h" -namespace kraken::binding::qjs { +namespace kraken { void bindTouchEvent(ExecutionContext* context); @@ -112,6 +112,6 @@ class TouchEventInstance : public EventInstance { friend TouchEvent; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_TOUCH_EVENTT_H diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc new file mode 100644 index 0000000000..85b2b0faff --- /dev/null +++ b/bridge/core/executing_context.cc @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "executing_context.h" +#include "built_in_string.h" +#include "core/dom/document.h" +#include "core/html/html_html_element.h" +#include "polyfill.h" +#include "qjs_window.h" + +#include "foundation/logging.h" + +namespace kraken { + +static std::atomic<int32_t> context_unique_id{0}; + +#define MAX_JS_CONTEXT 1024 +bool valid_contexts[MAX_JS_CONTEXT]; +std::atomic<uint32_t> running_context_list{0}; + +std::unique_ptr<ExecutingContext> createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) { + return std::make_unique<ExecutingContext>(contextId, handler, owner); +} + +ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) + : context_id_(contextId), handler_(handler), owner_(owner), ctx_invalid_(false), unique_id_(context_unique_id++) { +#if ENABLE_PROFILE + auto jsContextStartTime = + std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()) + .count(); + auto nativePerformance = Performance::instance(context_)->m_nativePerformance; + nativePerformance.mark(PERF_JS_CONTEXT_INIT_START, jsContextStartTime); + nativePerformance.mark(PERF_JS_CONTEXT_INIT_END); + nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_START); +#endif + + // @FIXME: maybe contextId will larger than MAX_JS_CONTEXT + valid_contexts[contextId] = true; + if (contextId > running_context_list) + running_context_list = contextId; + + time_origin_ = std::chrono::system_clock::now(); + + JSContext* ctx = script_state_.ctx(); + global_object_ = JS_GetGlobalObject(script_state_.ctx()); + + JS_SetContextOpaque(ctx, this); + JS_SetHostPromiseRejectionTracker(script_state_.runtime(), promiseRejectTracker, nullptr); + + // Register all built-in native bindings. + InstallBindings(this); + + // Install document. + InstallDocument(); + + // Binding global object and window. + InstallGlobal(); + +#if ENABLE_PROFILE + nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); + nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); +#endif + + initKrakenPolyFill(this); + + for (auto& p : pluginByteCode) { + EvaluateByteCode(p.second.bytes, p.second.length); + } + +#if ENABLE_PROFILE + nativePerformance.mark(PERF_JS_POLYFILL_INIT_END); +#endif +} + +ExecutingContext::~ExecutingContext() { + valid_contexts[context_id_] = false; + ctx_invalid_ = true; + + // Check if current context have unhandled exceptions. + JSValue exception = JS_GetException(script_state_.ctx()); + if (JS_IsObject(exception) || JS_IsException(exception)) { + // There must be bugs in native functions from call stack frame. Someone needs to fix it if throws. + ReportError(exception); + assert_m(false, "Unhandled exception found when Dispose JSContext."); + } + + JS_FreeValue(script_state_.ctx(), global_object_); +} + +ExecutingContext* ExecutingContext::From(JSContext* ctx) { + return static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx)); +} + +bool ExecutingContext::EvaluateJavaScript(const uint16_t* code, + size_t codeLength, + const char* sourceURL, + int startLine) { + std::string utf8Code = toUTF8(std::u16string(reinterpret_cast<const char16_t*>(code), codeLength)); + JSValue result = JS_Eval(script_state_.ctx(), utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); + DrainPendingPromiseJobs(); + bool success = HandleException(&result); + JS_FreeValue(script_state_.ctx(), result); + return success; +} + +bool ExecutingContext::EvaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine) { + std::string utf8Code = toUTF8(std::u16string(reinterpret_cast<const char16_t*>(code), length)); + JSValue result = JS_Eval(script_state_.ctx(), utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); + DrainPendingPromiseJobs(); + bool success = HandleException(&result); + JS_FreeValue(script_state_.ctx(), result); + return success; +} + +bool ExecutingContext::EvaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine) { + JSValue result = JS_Eval(script_state_.ctx(), code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL); + DrainPendingPromiseJobs(); + bool success = HandleException(&result); + JS_FreeValue(script_state_.ctx(), result); + return success; +} + +bool ExecutingContext::EvaluateByteCode(uint8_t* bytes, size_t byteLength) { + JSValue obj, val; + obj = JS_ReadObject(script_state_.ctx(), bytes, byteLength, JS_READ_OBJ_BYTECODE); + if (!HandleException(&obj)) + return false; + val = JS_EvalFunction(script_state_.ctx(), obj); + if (!HandleException(&val)) + return false; + JS_FreeValue(script_state_.ctx(), val); + return true; +} + +bool ExecutingContext::IsValid() const { + return !ctx_invalid_; +} + +void* ExecutingContext::owner() { + assert(!ctx_invalid_ && "GetExecutingContext has been released"); + return owner_; +} + +bool ExecutingContext::HandleException(JSValue* exc) { + if (JS_IsException(*exc)) { + JSValue error = JS_GetException(script_state_.ctx()); + ReportError(error); + DispatchGlobalErrorEvent(this, error); + JS_FreeValue(script_state_.ctx(), error); + return false; + } + + return true; +} + +bool ExecutingContext::HandleException(ScriptValue* exc) { + JSValue value = exc->QJSValue(); + return HandleException(&value); +} + +JSValue ExecutingContext::Global() { + return global_object_; +} + +JSContext* ExecutingContext::ctx() { + assert(!ctx_invalid_ && "GetExecutingContext has been released"); + return script_state_.ctx(); +} + +void ExecutingContext::ReportError(JSValueConst error) { + JSContext* ctx = script_state_.ctx(); + if (!JS_IsError(ctx, error)) + return; + + JSValue messageValue = JS_GetPropertyStr(ctx, error, "message"); + JSValue errorTypeValue = JS_GetPropertyStr(ctx, error, "name"); + const char* title = JS_ToCString(ctx, messageValue); + const char* type = JS_ToCString(ctx, errorTypeValue); + const char* stack = nullptr; + JSValue stackValue = JS_GetPropertyStr(ctx, error, "stack"); + if (!JS_IsUndefined(stackValue)) { + stack = JS_ToCString(ctx, stackValue); + } + + uint32_t messageLength = strlen(type) + strlen(title); + if (stack != nullptr) { + messageLength += 4 + strlen(stack); + char message[messageLength]; + sprintf(message, "%s: %s\n%s", type, title, stack); + handler_(this, message); + } else { + messageLength += 3; + char message[messageLength]; + sprintf(message, "%s: %s", type, title); + handler_(this, message); + } + + JS_FreeValue(ctx, errorTypeValue); + JS_FreeValue(ctx, messageValue); + JS_FreeValue(ctx, stackValue); + JS_FreeCString(ctx, title); + JS_FreeCString(ctx, stack); + JS_FreeCString(ctx, type); +} + +void ExecutingContext::DrainPendingPromiseJobs() { + // should executing pending promise jobs. + JSContext* pctx; + int finished = JS_ExecutePendingJob(script_state_.runtime(), &pctx); + while (finished != 0) { + finished = JS_ExecutePendingJob(script_state_.runtime(), &pctx); + if (finished == -1) { + break; + } + } + + // Throw error when promise are not handled. + rejected_promises_.Process(this); +} + +void ExecutingContext::DefineGlobalProperty(const char* prop, JSValue value) { + JSAtom atom = JS_NewAtom(script_state_.ctx(), prop); + JS_SetProperty(script_state_.ctx(), global_object_, atom, value); + JS_FreeAtom(script_state_.ctx(), atom); +} + +ExecutionContextData* ExecutingContext::contextData() { + return &context_data_; +} + +uint8_t* ExecutingContext::DumpByteCode(const char* code, + uint32_t codeLength, + const char* sourceURL, + size_t* bytecodeLength) { + JSValue object = + JS_Eval(script_state_.ctx(), code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY); + bool success = HandleException(&object); + if (!success) + return nullptr; + uint8_t* bytes = JS_WriteObject(script_state_.ctx(), bytecodeLength, object, JS_WRITE_OBJ_BYTECODE); + JS_FreeValue(script_state_.ctx(), object); + return bytes; +} + +void ExecutingContext::DispatchGlobalErrorEvent(ExecutingContext* context, JSValueConst error) { + // JSContext* ctx = context->ctx(); + // auto* window = static_cast<Window*>(JS_GetOpaque(context->global(), Window::classId())); + // + // { + // JSValue ErrorEventValue = JS_GetPropertyStr(ctx, context->global(), "ErrorEvent"); + // JSValue errorType = JS_NewString(ctx, "error"); + // JSValue errorInit = JS_NewObject(ctx); + // JS_SetPropertyStr(ctx, errorInit, "error", JS_DupValue(ctx, error)); + // JS_SetPropertyStr(ctx, errorInit, "message", JS_GetPropertyStr(ctx, error, "message")); + // JS_SetPropertyStr(ctx, errorInit, "lineno", JS_GetPropertyStr(ctx, error, "lineNumber")); + // JS_SetPropertyStr(ctx, errorInit, "filename", JS_GetPropertyStr(ctx, error, "fileName")); + // JS_SetPropertyStr(ctx, errorInit, "colno", JS_NewUint32(ctx, 0)); + // JSValue arguments[] = {errorType, errorInit}; + // JSValue errorEventValue = JS_CallConstructor(context->ctx(), ErrorEventValue, 2, arguments); + // if (JS_IsException(errorEventValue)) { + // context->handleException(&errorEventValue); + // return; + // } + // + // auto* errorEvent = static_cast<EventInstance*>(JS_GetOpaque(errorEventValue, Event::kEventClassID)); + // window->dispatchEvent(errorEvent); + // + // JS_FreeValue(ctx, ErrorEventValue); + // JS_FreeValue(ctx, errorEventValue); + // JS_FreeValue(ctx, errorType); + // JS_FreeValue(ctx, errorInit); + // + // context->drainPendingPromiseJobs(); + // } +} + +static void dispatchPromiseRejectionEvent(const char* eventType, + ExecutingContext* context, + JSValueConst promise, + JSValueConst error) { + // JSContext* ctx = context->ctx(); + // auto* window = static_cast<WindowInstance*>(JS_GetOpaque(context->global(), Window::classId())); + // + // // Trigger PromiseRejectionEvent(unhandledrejection) event. + // { + // JSValue PromiseRejectionEventValue = JS_GetPropertyStr(ctx, context->global(), "PromiseRejectionEvent"); + // JSValue errorType = JS_NewString(ctx, eventType); + // JSValue errorInit = JS_NewObject(ctx); + // JS_SetPropertyStr(ctx, errorInit, "promise", JS_DupValue(ctx, promise)); + // JS_SetPropertyStr(ctx, errorInit, "reason", JS_DupValue(ctx, error)); + // JSValue arguments[] = {errorType, errorInit}; + // JSValue rejectEventValue = JS_CallConstructor(context->ctx(), PromiseRejectionEventValue, 2, arguments); + // if (JS_IsException(rejectEventValue)) { + // context->handleException(&rejectEventValue); + // return; + // } + // + // auto* rejectEvent = static_cast<EventInstance*>(JS_GetOpaque(rejectEventValue, Event::kEventClassID)); + // window->dispatchEvent(rejectEvent); + // + // JS_FreeValue(ctx, errorType); + // JS_FreeValue(ctx, errorInit); + // JS_FreeValue(ctx, rejectEventValue); + // JS_FreeValue(ctx, PromiseRejectionEventValue); + // + // context->drainPendingPromiseJobs(); + // } +} + +void ExecutingContext::FlushUICommand() { + dartMethodPtr()->flushUICommand(context_id_); +} + +void ExecutingContext::DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, + JSValueConst promise, + JSValueConst error) { + // Trigger onerror event. + DispatchGlobalErrorEvent(context, error); + + // Trigger unhandledRejection event. + dispatchPromiseRejectionEvent("unhandledrejection", context, promise, error); +} + +void ExecutingContext::DispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValue promise, JSValue error) { + // Trigger rejectionhandled event. + dispatchPromiseRejectionEvent("rejectionhandled", context, promise, error); +} + +std::unordered_map<std::string, NativeByteCode> ExecutingContext::pluginByteCode{}; + +void ExecutingContext::promiseRejectTracker(JSContext* ctx, + JSValue promise, + JSValue reason, + int is_handled, + void* opaque) { + auto* context = static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx)); + // The unhandledrejection event is the promise-equivalent of the global error event, which is fired for uncaught + // exceptions. Because a rejected promise could be handled after the fact, by attaching catch(onRejected) or + // then(onFulfilled, onRejected) to it, the additional rejectionhandled event is needed to indicate that a promise + // which was previously rejected should no longer be considered unhandled. + if (is_handled) { + context->rejected_promises_.TrackHandledPromiseRejection(context, promise, reason); + } else { + context->rejected_promises_.TrackUnhandledPromiseRejection(context, promise, reason); + } +} + +DOMTimerCoordinator* ExecutingContext::Timers() { + return &timers_; +} + +ModuleListenerContainer* ExecutingContext::ModuleListeners() { + return &module_listener_container_; +} + +ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { + return &module_callbacks_; +} + +void ExecutingContext::SetMutationScope(MemberMutationScope& mutation_scope) { + // MemberMutationScope may be called by other MemberMutationScope in the call stack. + // Should save the tree corresponding to the call stack. + if (active_mutation_scope != nullptr) { + mutation_scope.SetParent(active_mutation_scope); + } + active_mutation_scope = &mutation_scope; +} + +void ExecutingContext::ClearMutationScope() { + active_mutation_scope = active_mutation_scope->Parent(); +} + +// PendingPromises* ExecutingContext::PendingPromises() { +// return &pending_promises_; +//} + +void ExecutingContext::InstallDocument() { + MemberMutationScope scope{this}; + document_ = MakeGarbageCollected<Document>(this); + document_->InitDocumentElement(); + DefineGlobalProperty("document", document_->ToQuickJS()); +} + +void ExecutingContext::InstallGlobal() { + MemberMutationScope mutation_scope{this}; + auto* window = MakeGarbageCollected<Window>(this); + JS_SetPrototype(ctx(), Global(), window->ToQuickJSUnsafe()); + JS_SetOpaque(Global(), window); +} + +// An lock free context validator. +bool isContextValid(int32_t contextId) { + if (contextId > running_context_list) + return false; + return valid_contexts[contextId]; +} + +} // namespace kraken diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h new file mode 100644 index 0000000000..8295b3ac27 --- /dev/null +++ b/bridge/core/executing_context.h @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_JS_CONTEXT_H +#define KRAKENBRIDGE_JS_CONTEXT_H + +#include <quickjs/list.h> +#include <quickjs/quickjs.h> +#include <atomic> +#include <cassert> +#include <cmath> +#include <cstring> +#include <locale> +#include <memory> +#include <mutex> +#include <unordered_map> +#include "bindings/qjs/binding_initializer.h" +#include "bindings/qjs/pending_promises.h" +#include "bindings/qjs/rejected_promises.h" +#include "bindings/qjs/script_value.h" +#include "foundation/macros.h" +#include "foundation/ui_command_buffer.h" + +#include "dart_methods.h" +#include "executing_context_data.h" +#include "frame/dom_timer_coordinator.h" +#include "frame/module_callback_coordinator.h" +#include "frame/module_listener_container.h" +#include "script_state.h" + +namespace kraken { + +struct NativeByteCode { + uint8_t* bytes; + int32_t length; +}; + +class ExecutingContext; +class Document; +class MemberMutationScope; + +using JSExceptionHandler = std::function<void(ExecutingContext* context, const char* message)>; + +bool isContextValid(int32_t contextId); + +// An environment in which script can execute. This class exposes the common +// properties of script execution environments on the kraken. +// Window : Document : ExecutionContext = 1 : 1 : 1 at any point in time. +class ExecutingContext { + public: + ExecutingContext() = delete; + ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); + ~ExecutingContext(); + + static ExecutingContext* From(JSContext* ctx); + + bool EvaluateJavaScript(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine); + 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; + JSValue Global(); + JSContext* ctx(); + FORCE_INLINE int32_t contextId() const { return context_id_; }; + void* owner(); + bool HandleException(JSValue* exc); + bool HandleException(ScriptValue* exc); + void ReportError(JSValueConst error); + void DrainPendingPromiseJobs(); + void DefineGlobalProperty(const char* prop, JSValueConst value); + ExecutionContextData* contextData(); + uint8_t* DumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength); + + // Make global object inherit from WindowProperties. + void InstallGlobal(); + + // Gets the DOMTimerCoordinator which maintains the "active timer + // list" of tasks created by setTimeout and setInterval. The + // DOMTimerCoordinator is owned by the ExecutionContext and should + // not be used after the ExecutionContext is destroyed. + DOMTimerCoordinator* Timers(); + + // Gets the ModuleListeners which registered by `kraken.addModuleListener API`. + ModuleListenerContainer* ModuleListeners(); + + // Gets the ModuleCallbacks which from the 4th parameter of `kraken.invokeModule` function. + ModuleCallbackCoordinator* ModuleCallbacks(); + + // Get all pending promises which are not resolved or rejected. + PendingPromises* GetPendingPromises() { return &pending_promises_; }; + + // Get current script state. + ScriptState* GetScriptState() { return &script_state_; } + + void SetMutationScope(MemberMutationScope& mutation_scope); + bool HasMutationScope() const { return active_mutation_scope != nullptr; } + MemberMutationScope* mutationScope() const { return active_mutation_scope; } + void ClearMutationScope(); + + FORCE_INLINE Document* document() { return document_; }; + FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &ui_command_buffer_; }; + FORCE_INLINE std::unique_ptr<DartMethodPointer>& dartMethodPtr() { return dart_method_ptr_; } + + // Force dart side to execute the pending ui commands. + void FlushUICommand(); + + static void DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, + JSValueConst promise, + JSValueConst error); + static void DispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error); + static void DispatchGlobalErrorEvent(ExecutingContext* context, JSValueConst error); + + // Bytecodes which registered by kraken plugins. + static std::unordered_map<std::string, NativeByteCode> pluginByteCode; + + private: + std::chrono::time_point<std::chrono::system_clock> time_origin_; + int32_t unique_id_; + + void InstallDocument(); + + static void promiseRejectTracker(JSContext* ctx, + JSValueConst promise, + JSValueConst reason, + JS_BOOL is_handled, + void* opaque); + + // 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. + ScriptState script_state_; + + int32_t context_id_; + JSExceptionHandler handler_; + void* owner_; + JSValue global_object_{JS_NULL}; + bool ctx_invalid_{false}; + Document* document_{nullptr}; + DOMTimerCoordinator timers_; + ModuleListenerContainer module_listener_container_; + ModuleCallbackCoordinator module_callbacks_; + ExecutionContextData context_data_{this}; + UICommandBuffer ui_command_buffer_{this}; + std::unique_ptr<DartMethodPointer> dart_method_ptr_ = std::make_unique<DartMethodPointer>(); + RejectedPromises rejected_promises_; + PendingPromises pending_promises_; + MemberMutationScope* active_mutation_scope{nullptr}; +}; + +class ObjectProperty { + KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(ObjectProperty); + + public: + ObjectProperty() = delete; + + // Define an property on object with a JSValue. + explicit ObjectProperty(ExecutingContext* context, JSValueConst thisObject, const char* property, JSValue value) + : m_value(value) { + JS_DefinePropertyValueStr(context->ctx(), thisObject, property, value, JS_PROP_ENUMERABLE); + } + + JSValue value() const { return m_value; } + + private: + JSValue m_value{JS_NULL}; +}; + +std::unique_ptr<ExecutingContext> createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); + +} // namespace kraken + +#endif // KRAKENBRIDGE_JS_CONTEXT_H diff --git a/bridge/core/executing_context_data.cc b/bridge/core/executing_context_data.cc new file mode 100644 index 0000000000..c968f2bd09 --- /dev/null +++ b/bridge/core/executing_context_data.cc @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "executing_context_data.h" +#include "executing_context.h" + +namespace kraken { + +JSValue ExecutionContextData::constructorForType(const WrapperTypeInfo* type) { + auto it = constructor_map_.find(type); + return it != constructor_map_.end() ? it->second : constructorForIdSlowCase(type); +} + +JSValue ExecutionContextData::prototypeForType(const WrapperTypeInfo* type) { + auto it = prototype_map_.find(type); + + // Constructor not initialized, create it. + if (it == prototype_map_.end()) { + constructorForIdSlowCase(type); + it = prototype_map_.find(type); + } + + return it != prototype_map_.end() ? it->second : JS_NULL; +} + +JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* type) { + JSContext* ctx = m_context->ctx(); + + JSClassID class_id{0}; + // Allocate a new unique classID from QuickJS. + JS_NewClassID(&class_id); + + assert(class_id > JS_CLASS_CUSTOM_CLASS_INIT_COUNT); + + // Create class template for behavior. + JSClassDef def{}; + def.class_name = type->className; + def.call = type->callFunc; + JS_NewClass(ScriptState::runtime(), class_id, &def); + + // Create class object and prototype object. + JSValue classObject = constructor_map_[type] = JS_NewObjectClass(m_context->ctx(), class_id); + JSValue prototypeObject = prototype_map_[type] = JS_NewObject(m_context->ctx()); + + // Make constructor function inherit to Function.prototype + JSValue functionConstructor = JS_GetPropertyStr(ctx, m_context->Global(), "Function"); + JSValue functionPrototype = JS_GetPropertyStr(ctx, functionConstructor, "prototype"); + JS_SetPrototype(ctx, classObject, functionPrototype); + JS_FreeValue(ctx, functionPrototype); + JS_FreeValue(ctx, functionConstructor); + + // Bind class object and prototype object. + JSAtom prototypeKey = JS_NewAtom(ctx, "prototype"); + JS_DefinePropertyValue(ctx, classObject, prototypeKey, prototypeObject, JS_PROP_C_W_E); + JS_FreeAtom(ctx, prototypeKey); + + // Inherit to parentClass. + if (type->parent_class != nullptr) { + assert(prototype_map_.count(type->parent_class) > 0); + JS_SetPrototype(m_context->ctx(), prototypeObject, prototype_map_[type->parent_class]); + } + + // Configure to be called as a constructor. + JS_SetConstructorBit(ctx, classObject, true); + + // Store WrapperTypeInfo as private data. + JS_SetOpaque(classObject, (void*)type); + + return classObject; +} + +void ExecutionContextData::Dispose() { + for (auto& entry : prototype_map_) { + JS_FreeValueRT(ScriptState::runtime(), entry.second); + } + + for (auto& entry : constructor_map_) { + JS_FreeValueRT(ScriptState::runtime(), entry.second); + } +} + +} // namespace kraken diff --git a/bridge/core/executing_context_data.h b/bridge/core/executing_context_data.h new file mode 100644 index 0000000000..ad88b2dcd0 --- /dev/null +++ b/bridge/core/executing_context_data.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CONTEXT_DATA_H +#define KRAKENBRIDGE_CONTEXT_DATA_H + +#include <quickjs/quickjs.h> +#include <unordered_map> +#include "bindings/qjs/wrapper_type_info.h" + +namespace kraken { + +class ExecutingContext; + +// Used to hold data that is associated with a single ExecutionContext object, and +// has a 1:1 relationship with ExecutionContext. +class ExecutionContextData final { + public: + explicit ExecutionContextData(ExecutingContext* context) : m_context(context){}; + ExecutionContextData(const ExecutionContextData&) = delete; + ExecutionContextData& operator=(const ExecutionContextData&) = delete; + + // Returns the constructor object that is appropriately initialized. + JSValue constructorForType(const WrapperTypeInfo* type); + // Returns the prototype object that is appropriately initialized. + JSValue prototypeForType(const WrapperTypeInfo* type); + + void Dispose(); + + private: + JSValue constructorForIdSlowCase(const WrapperTypeInfo* type); + std::unordered_map<const WrapperTypeInfo*, JSValue> constructor_map_; + std::unordered_map<const WrapperTypeInfo*, JSValue> prototype_map_; + + ExecutingContext* m_context; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CONTEXT_DATA_H diff --git a/bridge/bindings/qjs/js_context_test.cc b/bridge/core/executing_context_test.cc similarity index 84% rename from bridge/bindings/qjs/js_context_test.cc rename to bridge/core/executing_context_test.cc index 0c2cfa54f8..d21f44a5cc 100644 --- a/bridge/bindings/qjs/js_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -3,12 +3,21 @@ */ #include "gtest/gtest.h" +#include "include/kraken_bridge.h" #include "kraken_test_env.h" #include "page.h" +using namespace kraken; + TEST(Context, isValid) { - auto bridge = TEST_init(); - EXPECT_EQ(bridge->getContext()->isValid(), true); + { + auto bridge = TEST_init(); + EXPECT_EQ(bridge->GetExecutingContext()->IsValid(), true); + } + { + auto bridge = TEST_init(); + EXPECT_EQ(bridge->GetExecutingContext()->IsValid(), true); + } } TEST(Context, evalWithError) { @@ -94,7 +103,8 @@ TEST(Context, unrejectPromiseWillTriggerUnhandledRejectionEvent) { }; auto bridge = TEST_init(errorHandler); static int logIndex = 0; - static std::string logs[] = {"error event cannot read property 'forceNullError' of null", "unhandled event {promise: Promise {...}, reason: Error {...}} true"}; + static std::string logs[] = {"error event cannot read property 'forceNullError' of null", + "unhandled event {promise: Promise {...}, reason: Error {...}} true"}; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(logs[logIndex++].c_str(), message.c_str()); @@ -163,7 +173,9 @@ TEST(Context, unhandledRejectionEventWillTriggerWhenNotHandled) { static bool logCalled = false; auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; auto bridge = TEST_init(errorHandler); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; std::string code = R"( window.addEventListener('unhandledrejection', event => { @@ -192,7 +204,9 @@ TEST(Context, handledRejectionEventWillTriggerWhenUnHandledRejectHandled) { static bool logCalled = false; auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; auto bridge = TEST_init(errorHandler); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; std::string code = R"( window.addEventListener('unhandledrejection', event => { @@ -222,7 +236,7 @@ generateRejectedPromise(); )"; bridge->evaluateScript(code.c_str(), code.size(), "file://", 0); - TEST_runLoop(bridge->getContext()); + TEST_runLoop(bridge->GetExecutingContext()); EXPECT_EQ(errorHandlerExecuted, false); EXPECT_EQ(logCalled, true); kraken::KrakenPage::consoleMessageHandler = nullptr; @@ -261,7 +275,7 @@ TEST(Context, accessGetUICommandItemsAfterDisposed) { int32_t contextId; { auto bridge = TEST_init(); - contextId = bridge->getContext()->getContextId(); + contextId = bridge->GetExecutingContext()->contextId(); } EXPECT_EQ(getUICommandItems(contextId), nullptr); @@ -269,12 +283,12 @@ TEST(Context, accessGetUICommandItemsAfterDisposed) { TEST(Context, disposeContext) { initJSPagePool(1024 * 1024); - TEST_mockDartMethods(nullptr); + TEST_mockDartMethods(0, nullptr); uint32_t contextId = 0; auto bridge = static_cast<kraken::KrakenPage*>(getPage(contextId)); static bool disposed = false; bridge->disposeCallback = [](kraken::KrakenPage* bridge) { disposed = true; }; - disposePage(bridge->getContext()->getContextId()); + disposePage(bridge->GetExecutingContext()->contextId()); EXPECT_EQ(disposed, true); } @@ -310,7 +324,9 @@ TEST(Context, windowInheritEventTarget) { KRAKEN_LOG(VERBOSE) << errmsg; }; auto bridge = TEST_init(errorHandler); - const char* code = "console.log(window.addEventListener, addEventListener, globalThis.addEventListener, window.addEventListener === addEventListener)"; + const char* code = + "console.log(window.addEventListener, addEventListener, globalThis.addEventListener, window.addEventListener === " + "addEventListener)"; bridge->evaluateScript(code, strlen(code), "file://", 0); EXPECT_EQ(errorHandlerExecuted, false); EXPECT_EQ(logCalled, true); @@ -337,36 +353,39 @@ TEST(Context, evaluateByteCode) { TEST(jsValueToNativeString, utf8String) { auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); - JSValue str = JS_NewString(bridge->getContext()->ctx(), "helloworld"); - std::unique_ptr<NativeString> nativeString = kraken::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); - EXPECT_EQ(nativeString->length, 10); + JSValue str = JS_NewString(bridge->GetExecutingContext()->ctx(), "helloworld"); + std::unique_ptr<kraken::NativeString> nativeString = + kraken::jsValueToNativeString(bridge->GetExecutingContext()->ctx(), str); + EXPECT_EQ(nativeString->length(), 10); uint8_t expectedString[10] = {104, 101, 108, 108, 111, 119, 111, 114, 108, 100}; for (int i = 0; i < 10; i++) { - EXPECT_EQ(expectedString[i], *(nativeString->string + i)); + EXPECT_EQ(expectedString[i], *(nativeString->string() + i)); } - JS_FreeValue(bridge->getContext()->ctx(), str); + JS_FreeValue(bridge->GetExecutingContext()->ctx(), str); } TEST(jsValueToNativeString, unicodeChinese) { auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); - JSValue str = JS_NewString(bridge->getContext()->ctx(), "这是你的优乐美"); - std::unique_ptr<NativeString> nativeString = kraken::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); + JSValue str = JS_NewString(bridge->GetExecutingContext()->ctx(), "这是你的优乐美"); + std::unique_ptr<kraken::NativeString> nativeString = + kraken::jsValueToNativeString(bridge->GetExecutingContext()->ctx(), str); std::u16string expectedString = u"这是你的优乐美"; - EXPECT_EQ(nativeString->length, expectedString.size()); - for (int i = 0; i < nativeString->length; i++) { - EXPECT_EQ(expectedString[i], *(nativeString->string + i)); + EXPECT_EQ(nativeString->length(), expectedString.size()); + for (int i = 0; i < nativeString->length(); i++) { + EXPECT_EQ(expectedString[i], *(nativeString->string() + i)); } - JS_FreeValue(bridge->getContext()->ctx(), str); + JS_FreeValue(bridge->GetExecutingContext()->ctx(), str); } TEST(jsValueToNativeString, emoji) { auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); - JSValue str = JS_NewString(bridge->getContext()->ctx(), "……🤪"); - std::unique_ptr<NativeString> nativeString = kraken::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); + JSValue str = JS_NewString(bridge->GetExecutingContext()->ctx(), "……🤪"); + std::unique_ptr<kraken::NativeString> nativeString = + kraken::jsValueToNativeString(bridge->GetExecutingContext()->ctx(), str); std::u16string expectedString = u"……🤪"; - EXPECT_EQ(nativeString->length, expectedString.length()); - for (int i = 0; i < nativeString->length; i++) { - EXPECT_EQ(expectedString[i], *(nativeString->string + i)); + EXPECT_EQ(nativeString->length(), expectedString.length()); + for (int i = 0; i < nativeString->length(); i++) { + EXPECT_EQ(expectedString[i], *(nativeString->string() + i)); } - JS_FreeValue(bridge->getContext()->ctx(), str); + JS_FreeValue(bridge->GetExecutingContext()->ctx(), str); } diff --git a/bridge/core/fileapi/array_buffer_data.h b/bridge/core/fileapi/array_buffer_data.h new file mode 100644 index 0000000000..e90a8722c0 --- /dev/null +++ b/bridge/core/fileapi/array_buffer_data.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ +#define KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ + +namespace kraken { + +struct ArrayBufferData { + uint8_t* buffer; + int32_t length; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc new file mode 100644 index 0000000000..cb2d91b6cf --- /dev/null +++ b/bridge/core/fileapi/blob.cc @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "blob.h" +#include <string> +#include "bindings/qjs/script_promise_resolver.h" +#include "built_in_string.h" +#include "core/executing_context.h" + +namespace kraken { + +class BlobReaderClient { + public: + enum ReadType { kReadAsText, kReadAsArrayBuffer }; + + BlobReaderClient(ExecutingContext* context, + Blob* blob, + std::shared_ptr<ScriptPromiseResolver> resolver, + ReadType read_type) + : context_(context), blob_(blob), resolver_(std::move(resolver)), read_type_(read_type) { + Start(); + }; + + void Start(); + void DidFinishLoading(); + + private: + ExecutingContext* context_; + Blob* blob_; + std::shared_ptr<ScriptPromiseResolver> resolver_; + ReadType read_type_; +}; + +void BlobReaderClient::Start() { + // Use setTimeout to simulate async data loading. + // TODO: Blob are part of File API in W3C standard, but not supported by Kraken from now on. + // Needs to remove this after File API had landed. + auto callback = [](void* ptr, int32_t contextId, const char* errmsg) -> void { + auto* client = static_cast<BlobReaderClient*>(ptr); + client->DidFinishLoading(); + }; + context_->dartMethodPtr()->setTimeout(this, context_->contextId(), callback, 0); +} + +void BlobReaderClient::DidFinishLoading() { + if (read_type_ == ReadType::kReadAsText) { + resolver_->Resolve<std::string>(blob_->StringResult()); + } else if (read_type_ == ReadType::kReadAsArrayBuffer) { + resolver_->Resolve<ArrayBufferData>(blob_->ArrayBufferResult()); + } + delete this; +} + +Blob* Blob::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected<Blob>(context->ctx()); +} + +Blob* Blob::Create(ExecutingContext* context) { + return MakeGarbageCollected<Blob>(context->ctx()); +} + +Blob* Blob::Create(ExecutingContext* context, + std::vector<std::shared_ptr<BlobPart>>& data, + ExceptionState& exception_state) { + return MakeGarbageCollected<Blob>(context->ctx(), data); +} + +Blob* Blob::Create(ExecutingContext* context, + std::vector<std::shared_ptr<BlobPart>>& data, + std::shared_ptr<BlobPropertyBag> property, + ExceptionState& exception_state) { + return MakeGarbageCollected<Blob>(context->ctx(), data, property); +} + +int32_t Blob::size() { + return _data.size(); +} + +uint8_t* Blob::bytes() { + return _data.data(); +} + +void Blob::Trace(GCVisitor* visitor) const {} + +Blob* Blob::slice(ExceptionState& exception_state) { + return slice(0, _data.size(), exception_state); +} +Blob* Blob::slice(int64_t start, ExceptionState& exception_state) { + return slice(start, _data.size(), exception_state); +} +Blob* Blob::slice(int64_t start, int64_t end, ExceptionState& exception_state) { + return slice(start, end, AtomicString::Empty(ctx()), exception_state); +} +Blob* Blob::slice(int64_t start, int64_t end, const AtomicString& content_type, ExceptionState& exception_state) { + auto* newBlob = MakeGarbageCollected<Blob>(ctx()); + std::vector<uint8_t> newData; + newData.reserve(_data.size() - (end - start)); + newData.insert(newData.begin(), _data.begin() + start, _data.end() - (_data.size() - end)); + newBlob->_data = newData; + newBlob->mime_type_ = content_type != built_in_string::kempty_string ? content_type.ToStdString() : mime_type_; + return newBlob; +} + +std::string Blob::StringResult() { + return std::string(bytes(), bytes() + size()); +} + +ArrayBufferData Blob::ArrayBufferResult() { + return ArrayBufferData{bytes(), size()}; +} + +std::string Blob::type() { + return mime_type_; +} + +ScriptPromise Blob::arrayBuffer(ExceptionState& exception_state) { + auto resolver = ScriptPromiseResolver::Create(GetExecutingContext()); + new BlobReaderClient(GetExecutingContext(), this, resolver, BlobReaderClient::ReadType::kReadAsArrayBuffer); + return resolver->Promise(); +} + +ScriptPromise Blob::text(ExceptionState& exception_state) { + auto resolver = ScriptPromiseResolver::Create(GetExecutingContext()); + new BlobReaderClient(GetExecutingContext(), this, resolver, BlobReaderClient::ReadType::kReadAsText); + return resolver->Promise(); +} + +void Blob::PopulateBlobData(std::vector<std::shared_ptr<BlobPart>>& data) { + for (auto& item : data) { + switch (item->GetContentType()) { + case BlobPart::ContentType::kString: { + AppendText(item->GetString()); + break; + } + case BlobPart::ContentType::kArrayBuffer: + case BlobPart::ContentType::kArrayBufferView: { + uint32_t length; + uint8_t* buffer = item->GetBytes(&length); + AppendBytes(buffer, length); + break; + } + case BlobPart::ContentType::kBlob: { + AppendBytes(item->GetBlob()->bytes(), item->GetBlob()->size()); + break; + } + } + } +} + +void Blob::AppendText(const std::string& string) { + std::vector<uint8_t> strArr(string.begin(), string.end()); + _data.reserve(_data.size() + strArr.size()); + _data.insert(_data.end(), strArr.begin(), strArr.end()); +} + +void Blob::AppendBytes(uint8_t* buffer, uint32_t length) { + _data.reserve(_data.size() + length); + for (size_t i = 0; i < length; i++) { + _data.emplace_back(buffer[i]); + } +} + +} // namespace kraken diff --git a/bridge/core/fileapi/blob.d.ts b/bridge/core/fileapi/blob.d.ts new file mode 100644 index 0000000000..468118ef47 --- /dev/null +++ b/bridge/core/fileapi/blob.d.ts @@ -0,0 +1,8 @@ +interface Blob { + readonly size: number; + readonly type: string; + arrayBuffer(): Promise<ArrayBuffer>; + slice(start?: int64, end?: int64, contentType?: string): Blob; + text(): Promise<string>; + new(blobParts?: BlobPart[], options?: BlobPropertyBag): Blob; +} diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h new file mode 100644 index 0000000000..30db15ed3e --- /dev/null +++ b/bridge/core/fileapi/blob.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BLOB_H +#define KRAKENBRIDGE_BLOB_H + +#include <string> +#include <vector> +#include "array_buffer_data.h" +#include "bindings/qjs/macros.h" +#include "bindings/qjs/script_promise.h" +#include "bindings/qjs/script_wrappable.h" +#include "blob_part.h" +#include "blob_property_bag.h" + +namespace kraken { + +class Blob : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + static Blob* Create(ExecutingContext* context, ExceptionState& exception_state); + static Blob* Create(ExecutingContext* context); + static Blob* Create(ExecutingContext* context, + std::vector<std::shared_ptr<BlobPart>>& data, + ExceptionState& exception_state); + static Blob* Create(ExecutingContext* context, + std::vector<std::shared_ptr<BlobPart>>& data, + std::shared_ptr<BlobPropertyBag> property, + ExceptionState& exception_state); + + Blob() = delete; + explicit Blob(JSContext* ctx) : ScriptWrappable(ctx){}; + explicit Blob(JSContext* ctx, std::vector<std::shared_ptr<BlobPart>>& data) : ScriptWrappable(ctx) { + PopulateBlobData(data); + }; + explicit Blob(JSContext* ctx, + std::vector<std::shared_ptr<BlobPart>>& data, + std::shared_ptr<BlobPropertyBag>& property) + : mime_type_(property->type()), ScriptWrappable(ctx) { + PopulateBlobData(data); + }; + + void AppendText(const std::string& string); + void AppendBytes(uint8_t* buffer, uint32_t length); + + /// get an pointer of bytes data from JSBlob + uint8_t* bytes(); + /// get bytes data's length + int32_t size(); + std::string type(); + + ScriptPromise arrayBuffer(ExceptionState& exception_state); + ScriptPromise text(ExceptionState& exception_state); + + Blob* slice(ExceptionState& exception_state); + Blob* slice(int64_t start, ExceptionState& exception_state); + Blob* slice(int64_t start, int64_t end, ExceptionState& exception_state); + Blob* slice(int64_t start, int64_t end, const AtomicString& content_type, ExceptionState& exception_state); + + std::string StringResult(); + ArrayBufferData ArrayBufferResult(); + + void Trace(GCVisitor* visitor) const override; + + protected: + void PopulateBlobData(std::vector<std::shared_ptr<BlobPart>>& data); + + private: + std::string mime_type_; + std::vector<uint8_t> _data; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BLOB_H diff --git a/bridge/core/fileapi/blob_part.cc b/bridge/core/fileapi/blob_part.cc new file mode 100644 index 0000000000..a042475df1 --- /dev/null +++ b/bridge/core/fileapi/blob_part.cc @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "blob_part.h" +#include "qjs_blob.h" + +namespace kraken { + +std::shared_ptr<BlobPart> BlobPart::Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + auto* context = ExecutingContext::From(ctx); + // Create from string. + if (JS_IsString(value)) { + const char* buffer = JS_ToCString(ctx, value); + auto result = std::make_shared<BlobPart>(ctx, buffer); + JS_FreeCString(ctx, buffer); + return result; + } + + // Create from another blob + if (QJSBlob::HasInstance(context, value)) { + Blob* qjs_value = toScriptWrappable<Blob>(value); + return std::make_shared<BlobPart>(ctx, qjs_value); + } + + if (JS_IsArrayBuffer(value)) { + size_t length; + uint8_t* buffer = JS_GetArrayBuffer(ctx, &length, value); + return std::make_shared<BlobPart>(ctx, buffer, length); + } + + if (JS_IsArrayBufferView(value)) { + size_t byte_offset; + size_t byte_length; + size_t byte_per_element; + size_t length; + uint8_t* buffer; + JSValue arrayBufferObject = JS_GetTypedArrayBuffer(ctx, value, &byte_offset, &byte_length, &byte_per_element); + if (JS_IsException(arrayBufferObject)) { + exception_state.ThrowException(ctx, arrayBufferObject); + return nullptr; + } + buffer = JS_GetArrayBuffer(ctx, &length, arrayBufferObject); + return std::make_shared<BlobPart>(ctx, buffer, length, byte_offset, byte_length, byte_per_element); + } + + return nullptr; +} + +JSValue BlobPart::ToQuickJS(JSContext* ctx) const { + switch (content_type_) { + case ContentType::kString: { + return JS_NewString(ctx, member_string_.c_str()); + } + case ContentType::kBlob: { + return blob_->ToQuickJS(); + } + case ContentType::kArrayBuffer: { + return JS_NewArrayBufferCopy(ctx, bytes_, byte_length_); + } + case ContentType::kArrayBufferView: { + // TODO: Create ArrayBufferView from QuickJS API is not support now. + return JS_NULL; + } + } +} + +BlobPart::ContentType BlobPart::GetContentType() const { + return content_type_; +} + +const std::string& BlobPart::GetString() const { + return member_string_; +} + +uint8_t* BlobPart::GetBytes(uint32_t* length) const { + *length = byte_length_; + return bytes_; +} + +Blob* BlobPart::GetBlob() const { + return blob_; +} + +} // namespace kraken diff --git a/bridge/core/fileapi/blob_part.h b/bridge/core/fileapi/blob_part.h new file mode 100644 index 0000000000..b734bcb802 --- /dev/null +++ b/bridge/core/fileapi/blob_part.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ +#define KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ + +#include <quickjs/quickjs.h> +#include <memory> +#include <string> +#include <utility> +#include "bindings/qjs/exception_state.h" + +namespace kraken { + +class Blob; + +class BlobPart { + public: + using ImplType = std::shared_ptr<BlobPart>; + + enum class ContentType { kArrayBuffer, kArrayBufferView, kBlob, kString }; + + static std::shared_ptr<BlobPart> Create(JSContext* ctx, JSValue value, ExceptionState& exception_state); + + JSValue ToQuickJS(JSContext* ctx) const; + ContentType GetContentType() const; + const std::string& GetString() const; + uint8_t* GetBytes(uint32_t* length) const; + Blob* GetBlob() const; + + explicit BlobPart(JSContext* ctx, uint8_t* arrayBuffer, uint32_t length) + : content_type_(ContentType::kArrayBuffer), bytes_(arrayBuffer), byte_length_(length){}; + explicit BlobPart(JSContext* ctx, + uint8_t* buffer, + uint32_t length, + size_t byte_offset, + size_t byte_length, + size_t byte_per_element) + : content_type_(ContentType::kArrayBufferView), bytes_(buffer), byte_length_(length){}; + explicit BlobPart(JSContext* ctx, std::string value) + : content_type_(ContentType::kString), member_string_(std::move(value)){}; + explicit BlobPart(JSContext* ctx, Blob* blob) : content_type_(ContentType::kBlob), blob_(blob){}; + + private: + ContentType content_type_; + std::string member_string_; + Blob* blob_{nullptr}; + uint8_t* bytes_{nullptr}; + uint32_t byte_length_{0}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ diff --git a/bridge/core/fileapi/blob_property_bag.cc b/bridge/core/fileapi/blob_property_bag.cc new file mode 100644 index 0000000000..422d205041 --- /dev/null +++ b/bridge/core/fileapi/blob_property_bag.cc @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "blob_property_bag.h" + +namespace kraken { + +std::shared_ptr<BlobPropertyBag> BlobPropertyBag::Create(JSContext* ctx, + JSValue value, + ExceptionState& exceptionState) { + auto bag = std::make_shared<BlobPropertyBag>(); + bag->FillMemberFromQuickjsObject(ctx, value, exceptionState); + return nullptr; +} + +void BlobPropertyBag::FillMemberFromQuickjsObject(JSContext* ctx, JSValue value, ExceptionState& exceptionState) { + if (!JS_IsObject(value)) { + return; + } + + JSValue typeValue = JS_GetPropertyStr(ctx, value, "type"); + const char* ctype = JS_ToCString(ctx, typeValue); + m_type = std::string(ctype); + + JS_FreeCString(ctx, ctype); + JS_FreeValue(ctx, typeValue); +} + +} // namespace kraken diff --git a/bridge/core/fileapi/blob_property_bag.h b/bridge/core/fileapi/blob_property_bag.h new file mode 100644 index 0000000000..452417d009 --- /dev/null +++ b/bridge/core/fileapi/blob_property_bag.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ +#define KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ + +#include <quickjs/quickjs.h> +#include <memory> +#include "core/executing_context.h" + +namespace kraken { + +class BlobPropertyBag final { + public: + using ImplType = std::shared_ptr<BlobPropertyBag>; + + static std::shared_ptr<BlobPropertyBag> Create(JSContext* ctx, JSValue value, ExceptionState& exceptionState); + + const std::string& type() const { return m_type; } + + private: + void FillMemberFromQuickjsObject(JSContext* ctx, JSValue value, ExceptionState& exceptionState); + std::string m_type; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc new file mode 100644 index 0000000000..9b9039b4e1 --- /dev/null +++ b/bridge/core/frame/console.cc @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "console.h" +#include <sstream> +#include "built_in_string.h" +#include "foundation/logging.h" + +namespace kraken { + +void Console::__kraken_print__(ExecutingContext* context, + const AtomicString& log, + const AtomicString& level, + ExceptionState& exception) { + std::stringstream stream; + std::string buffer = log.ToStdString(); + stream << buffer; + printLog(context, stream, level != built_in_string::kempty_string ? level.ToStdString() : "info", nullptr); +} + +void Console::__kraken_print__(ExecutingContext* context, const AtomicString& log, ExceptionState& exception_state) { + std::stringstream stream; + std::string buffer = log.ToStdString(); + stream << buffer; + printLog(context, stream, "info", nullptr); +} + +} // namespace kraken diff --git a/bridge/core/frame/console.d.ts b/bridge/core/frame/console.d.ts new file mode 100644 index 0000000000..1e2d5a4472 --- /dev/null +++ b/bridge/core/frame/console.d.ts @@ -0,0 +1 @@ +declare const __kraken_print__: (log: string, level?: string) => void; diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h new file mode 100644 index 0000000000..461d0c0915 --- /dev/null +++ b/bridge/core/frame/console.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKE_CONSOLE_H +#define KRAKE_CONSOLE_H + +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/script_value.h" +#include "core/executing_context.h" + +namespace kraken { + +class Console final { + public: + static void __kraken_print__(ExecutingContext* context, + const AtomicString& log, + const AtomicString& level, + ExceptionState& exception); + static void __kraken_print__(ExecutingContext* context, const AtomicString& log, ExceptionState& exception_state); +}; + +} // namespace kraken + +#endif // KRAKE_CONSOLE_H diff --git a/bridge/bindings/qjs/bom/console_test.cc b/bridge/core/frame/console_test.cc similarity index 95% rename from bridge/bindings/qjs/bom/console_test.cc rename to bridge/core/frame/console_test.cc index b3fcceec18..502b6f2799 100644 --- a/bridge/bindings/qjs/bom/console_test.cc +++ b/bridge/core/frame/console_test.cc @@ -5,9 +5,8 @@ #include "console.h" #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "page.h" -std::once_flag kGlobalClassIdFlag; +using namespace kraken; TEST(Console, rawPrintShouldWork) { static bool logExecuted = false; diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc new file mode 100644 index 0000000000..8d3f92bd8c --- /dev/null +++ b/bridge/core/frame/dom_timer.cc @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "dom_timer.h" + +#include <utility> +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "bindings/qjs/qjs_engine_patch.h" +#include "core/executing_context.h" + +#if UNIT_TEST +#include "kraken_test_env.h" +#endif + +namespace kraken { + +std::shared_ptr<DOMTimer> DOMTimer::create(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback) { + return std::make_shared<DOMTimer>(context, callback); +} + +DOMTimer::DOMTimer(ExecutingContext* context, std::shared_ptr<QJSFunction> callback) + : context_(context), callback_(std::move(callback)), status_(TimerStatus::kPending) {} + +void DOMTimer::Fire() { + if (!callback_->IsFunction(context_->ctx())) + return; + + ScriptValue returnValue = callback_->Invoke(context_->ctx(), ScriptValue::Empty(context_->ctx()), 0, nullptr); + + if (returnValue.IsException()) { + context_->HandleException(&returnValue); + } +} + +void DOMTimer::setTimerId(int32_t timerId) { + timerId_ = timerId; +} + +} // namespace kraken diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h new file mode 100644 index 0000000000..225dd75fbb --- /dev/null +++ b/bridge/core/frame/dom_timer.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_DOM_TIMER_H +#define KRAKENBRIDGE_DOM_TIMER_H + +#include "bindings/qjs/qjs_function.h" +#include "bindings/qjs/script_wrappable.h" +#include "dom_timer_coordinator.h" + +namespace kraken { + +class DOMTimer { + public: + enum TimerStatus { kPending, kExecuting, kFinished }; + + static std::shared_ptr<DOMTimer> create(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback); + DOMTimer(ExecutingContext* context, std::shared_ptr<QJSFunction> callback); + + // Trigger timer callback. + void Fire(); + + [[nodiscard]] int32_t timerId() const { return timerId_; }; + void setTimerId(int32_t timerId); + + void SetStatus(TimerStatus status) { status_ = status; } + [[nodiscard]] TimerStatus status() const { return status_; } + + ExecutingContext* context() { return context_; } + + private: + ExecutingContext* context_{nullptr}; + int32_t timerId_{-1}; + int32_t isInterval_{false}; + TimerStatus status_; + std::shared_ptr<QJSFunction> callback_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_DOM_TIMER_H diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc new file mode 100644 index 0000000000..c043b25c95 --- /dev/null +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "dom_timer_coordinator.h" +#include "core/dart_methods.h" +#include "core/executing_context.h" +#include "dom_timer.h" + +#if UNIT_TEST +#include "kraken_test_env.h" +#endif + +namespace kraken { + +static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { + auto* context = timer->context(); + + if (errmsg != nullptr) { + JSValue exception = JS_ThrowTypeError(timer->context()->ctx(), "%s", errmsg); + context->HandleException(&exception); + return; + } + + // Trigger timer callbacks. + timer->Fire(); + + // Executing pending async jobs. + context->DrainPendingPromiseJobs(); +} + +static void handleTransientCallback(void* ptr, int32_t contextId, const char* errmsg) { + auto* timer = static_cast<DOMTimer*>(ptr); + auto* context = timer->context(); + + if (!context->IsValid()) + return; + + handleTimerCallback(timer, errmsg); + + context->Timers()->removeTimeoutById(timer->timerId()); +} + +void DOMTimerCoordinator::installNewTimer(ExecutingContext* context, int32_t timerId, std::shared_ptr<DOMTimer> timer) { + m_activeTimers[timerId] = timer; +} + +void* DOMTimerCoordinator::removeTimeoutById(int32_t timerId) { + if (m_activeTimers.count(timerId) == 0) + return nullptr; + auto timer = m_activeTimers[timerId]; + + if (timer->status() == DOMTimer::kPending) { + m_activeTimers.erase(timerId); + } + + return nullptr; +} + +std::shared_ptr<DOMTimer> DOMTimerCoordinator::getTimerById(int32_t timerId) { + if (m_activeTimers.count(timerId) == 0) + return nullptr; + return m_activeTimers[timerId]; +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/bom/dom_timer_coordinator.h b/bridge/core/frame/dom_timer_coordinator.h similarity index 70% rename from bridge/bindings/qjs/bom/dom_timer_coordinator.h rename to bridge/core/frame/dom_timer_coordinator.h index c8229620f3..40c4e58a8c 100644 --- a/bridge/bindings/qjs/bom/dom_timer_coordinator.h +++ b/bridge/core/frame/dom_timer_coordinator.h @@ -9,10 +9,10 @@ #include <unordered_map> #include <vector> -namespace kraken::binding::qjs { +namespace kraken { -class ExecutionContext; class DOMTimer; +class ExecutingContext; // Maintains a set of DOMTimers for a given page // DOMTimerCoordinator assigns IDs to timers; these IDs are @@ -22,20 +22,17 @@ class DOMTimer; class DOMTimerCoordinator { public: // Creates and installs a new timer. Returns the assigned ID. - void installNewTimer(ExecutionContext* context, int32_t timerId, DOMTimer* timer); + void installNewTimer(ExecutingContext* context, int32_t timerId, std::shared_ptr<DOMTimer> timer); // Removes and disposes the timer with the specified ID, if any. This may // destroy the timer. void* removeTimeoutById(int32_t timerId); - DOMTimer* getTimerById(int32_t timerId); - - void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func); + std::shared_ptr<DOMTimer> getTimerById(int32_t timerId); private: - std::unordered_map<int, DOMTimer*> m_activeTimers; - std::vector<DOMTimer*> m_abandonedTimers; + std::unordered_map<int, std::shared_ptr<DOMTimer>> m_activeTimers; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_BOM_DOM_TIMER_COORDINATOR_H_ diff --git a/bridge/bindings/qjs/bom/timer_test.cc b/bridge/core/frame/dom_timer_test.cc similarity index 68% rename from bridge/bindings/qjs/bom/timer_test.cc rename to bridge/core/frame/dom_timer_test.cc index 7261b2f4e7..ad3534ba5a 100644 --- a/bridge/bindings/qjs/bom/timer_test.cc +++ b/bridge/core/frame/dom_timer_test.cc @@ -3,9 +3,9 @@ */ #include "gtest/gtest.h" -#include "kraken_bridge.h" #include "kraken_test_env.h" -#include "page.h" + +using namespace kraken; TEST(Timer, setTimeout) { auto bridge = TEST_init(); @@ -36,14 +36,16 @@ console.log('1234'); )"; bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - TEST_runLoop(bridge->getContext()); - disposePage(0); + TEST_runLoop(bridge->GetExecutingContext()); } TEST(Timer, clearTimeout) { auto bridge = TEST_init(); + static bool log_called = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + log_called = true; + }; std::string code = R"( function getCachedData() { @@ -62,6 +64,22 @@ clearTimeout(timer); )"; bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - TEST_runLoop(bridge->getContext()); - disposePage(0); + TEST_runLoop(bridge->GetExecutingContext()); + + EXPECT_EQ(log_called, false); +} + +TEST(Timer, clearTimeoutWhenSetTimeout) { + auto bridge = TEST_init(); + + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + + std::string code = R"( +let timer = setTimeout(() => { + clearTimeout(timer); +}, 10); +)"; + + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + TEST_runLoop(bridge->GetExecutingContext()); } diff --git a/bridge/core/frame/legacy/location.cc b/bridge/core/frame/legacy/location.cc new file mode 100644 index 0000000000..2767efeecf --- /dev/null +++ b/bridge/core/frame/legacy/location.cc @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "location.h" +#include "core/executing_context.h" + +namespace kraken { + +void Location::__kraken_location_reload__(ExecutingContext* context, ExceptionState& exception_state) { + if (context->dartMethodPtr()->reloadApp == nullptr) { + exception_state.ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'reload': dart method (reloadApp) is not registered."); + return; + } + + context->FlushUICommand(); + context->dartMethodPtr()->reloadApp(context->contextId()); +} + +} // namespace kraken diff --git a/bridge/core/frame/legacy/location.d.ts b/bridge/core/frame/legacy/location.d.ts new file mode 100644 index 0000000000..8567de2aea --- /dev/null +++ b/bridge/core/frame/legacy/location.d.ts @@ -0,0 +1 @@ +declare const __kraken_location_reload__: () => void; diff --git a/bridge/core/frame/legacy/location.h b/bridge/core/frame/legacy/location.h new file mode 100644 index 0000000000..86c2be89d5 --- /dev/null +++ b/bridge/core/frame/legacy/location.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_LOCATION_H +#define KRAKENBRIDGE_LOCATION_H + +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/script_wrappable.h" + +namespace kraken { + +class Location { + public: + static void __kraken_location_reload__(ExecutingContext* context, ExceptionState& exception_state); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_LOCATION_H diff --git a/bridge/core/frame/module_callback.cc b/bridge/core/frame/module_callback.cc new file mode 100644 index 0000000000..552f3ceb05 --- /dev/null +++ b/bridge/core/frame/module_callback.cc @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "module_callback.h" + +namespace kraken { + +std::shared_ptr<ModuleCallback> ModuleCallback::Create(std::shared_ptr<QJSFunction> function) { + return std::make_shared<ModuleCallback>(function); +} + +ModuleCallback::ModuleCallback(std::shared_ptr<QJSFunction> function) : function_(function) {} + +std::shared_ptr<QJSFunction> ModuleCallback::value() { + return function_; +} + +} // namespace kraken diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h new file mode 100644 index 0000000000..2399f8f1b0 --- /dev/null +++ b/bridge/core/frame/module_callback.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MODULE_CALLBACK_H +#define KRAKENBRIDGE_MODULE_CALLBACK_H + +#include <quickjs/list.h> +#include "bindings/qjs/qjs_function.h" + +namespace kraken { + +// ModuleCallback is an asynchronous callback function, usually from the 4th parameter of `kraken.invokeModule` +// function. When the asynchronous operation on the Dart side ends, the callback is will called and to return to the JS +// executing environment. +class ModuleCallback { + public: + static std::shared_ptr<ModuleCallback> Create(std::shared_ptr<QJSFunction> function); + explicit ModuleCallback(std::shared_ptr<QJSFunction> function); + + std::shared_ptr<QJSFunction> value(); + + private: + std::shared_ptr<QJSFunction> function_{nullptr}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_MODULE_CALLBACK_H diff --git a/bridge/core/frame/module_callback_coordinator.cc b/bridge/core/frame/module_callback_coordinator.cc new file mode 100644 index 0000000000..835ae00022 --- /dev/null +++ b/bridge/core/frame/module_callback_coordinator.cc @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "module_callback_coordinator.h" + +namespace kraken { + +void ModuleCallbackCoordinator::AddModuleCallbacks(std::shared_ptr<ModuleCallback>&& callback) { + listeners_.push_front(callback); +} + +void ModuleCallbackCoordinator::RemoveModuleCallbacks(std::shared_ptr<ModuleCallback> callback) { + listeners_.remove(callback); +} + +const std::forward_list<std::shared_ptr<ModuleCallback>>* ModuleCallbackCoordinator::listeners() const { + return &listeners_; +} + +ModuleCallbackCoordinator::ModuleCallbackCoordinator() {} + +} // namespace kraken diff --git a/bridge/core/frame/module_callback_coordinator.h b/bridge/core/frame/module_callback_coordinator.h new file mode 100644 index 0000000000..f858bd93ee --- /dev/null +++ b/bridge/core/frame/module_callback_coordinator.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MODULE_CALLBACK_COORDINATOR_H +#define KRAKENBRIDGE_MODULE_CALLBACK_COORDINATOR_H + +#include <forward_list> +// Quickjs's linked-list are more efficient than STL forward_list. +#include <quickjs/list.h> +#include "module_callback.h" +#include "module_manager.h" + +namespace kraken { + +class ModuleListener; + +class ModuleCallbackCoordinator final { + public: + ModuleCallbackCoordinator(); + + void AddModuleCallbacks(std::shared_ptr<ModuleCallback>&& callback); + void RemoveModuleCallbacks(std::shared_ptr<ModuleCallback> callback); + + [[nodiscard]] const std::forward_list<std::shared_ptr<ModuleCallback>>* listeners() const; + + private: + std::forward_list<std::shared_ptr<ModuleCallback>> listeners_; + friend ModuleListener; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_MODULE_CALLBACK_COORDINATOR_H diff --git a/bridge/core/frame/module_listener.cc b/bridge/core/frame/module_listener.cc new file mode 100644 index 0000000000..2ef03d4fe0 --- /dev/null +++ b/bridge/core/frame/module_listener.cc @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "module_listener.h" + +#include <utility> + +namespace kraken { + +std::shared_ptr<ModuleListener> ModuleListener::Create(const std::shared_ptr<QJSFunction>& function) { + return std::make_shared<ModuleListener>(function); +} + +ModuleListener::ModuleListener(std::shared_ptr<QJSFunction> function) : function_(std::move(function)) {} + +} // namespace kraken diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h new file mode 100644 index 0000000000..e0efce08b1 --- /dev/null +++ b/bridge/core/frame/module_listener.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MODULE_LISTENER_H +#define KRAKENBRIDGE_MODULE_LISTENER_H + +#include "bindings/qjs/qjs_function.h" + +namespace kraken { + +class ModuleCallbackCoordinator; +class ModuleListenerContainer; + +// ModuleListener is an persistent callback function. Registered from user with `kraken.addModuleListener` method. +// When module event triggered at dart side, All module listener will be invoked and let user to dispatch further +// operations. +class ModuleListener { + public: + static std::shared_ptr<ModuleListener> Create(const std::shared_ptr<QJSFunction>& function); + explicit ModuleListener(std::shared_ptr<QJSFunction> function); + + private: + std::shared_ptr<QJSFunction> function_{nullptr}; + + friend ModuleListenerContainer; + friend ModuleCallbackCoordinator; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_MODULE_LISTENER_H diff --git a/bridge/core/frame/module_listener_container.cc b/bridge/core/frame/module_listener_container.cc new file mode 100644 index 0000000000..c7714c0809 --- /dev/null +++ b/bridge/core/frame/module_listener_container.cc @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "module_listener_container.h" + +namespace kraken { + +void ModuleListenerContainer::AddModuleListener(const std::shared_ptr<ModuleListener>& listener) { + listeners_.push_front(listener); +} + +} // namespace kraken diff --git a/bridge/core/frame/module_listener_container.h b/bridge/core/frame/module_listener_container.h new file mode 100644 index 0000000000..615dd8bda0 --- /dev/null +++ b/bridge/core/frame/module_listener_container.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H +#define KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H + +#include <forward_list> +#include "module_listener.h" + +namespace kraken { + +class ModuleListenerContainer final { + public: + void AddModuleListener(const std::shared_ptr<ModuleListener>& listener); + + private: + std::forward_list<std::shared_ptr<ModuleListener>> listeners_; + friend ModuleListener; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc new file mode 100644 index 0000000000..63f6e3101d --- /dev/null +++ b/bridge/core/frame/module_manager.cc @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "module_manager.h" +#include "core/executing_context.h" +#include "module_callback.h" + +namespace kraken { + +struct ModuleContext { + ExecutingContext* context; + std::shared_ptr<ModuleCallback> callback; +}; + +void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const char* errmsg, NativeString* json) { + auto* moduleContext = static_cast<ModuleContext*>(ptr); + ExecutingContext* context = moduleContext->context; + + if (!context->IsValid()) + return; + + if (moduleContext->callback == nullptr) { + JSValue exception = JS_ThrowTypeError(moduleContext->context->ctx(), + "Failed to execute '__kraken_invoke_module__': callback is null."); + context->HandleException(&exception); + return; + } + + JSContext* ctx = moduleContext->context->ctx(); + + 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); + } + } else { + std::u16string argumentString = std::u16string(reinterpret_cast<const char16_t*>(json->string()), json->length()); + std::string utf8Arguments = toUTF8(argumentString); + ScriptValue jsonObject = ScriptValue::CreateJsonObject(ctx, utf8Arguments.c_str(), utf8Arguments.size()); + ScriptValue arguments[] = {jsonObject}; + ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, ScriptValue::Empty(ctx), 1, arguments); + if (returnValue.IsException()) { + context->HandleException(&returnValue); + } + } + + context->DrainPendingPromiseJobs(); + context->ModuleCallbacks()->RemoveModuleCallbacks(moduleContext->callback); + + delete moduleContext; +} + +void handleInvokeModuleUnexpectedCallback(void* callbackContext, + int32_t contextId, + const char* errmsg, + NativeString* json) { + static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); +} + +AtomicString ModuleManager::__kraken_invoke_module__(ExecutingContext* context, + const AtomicString& moduleName, + const AtomicString& method, + ExceptionState& exception) { + ScriptValue empty = ScriptValue::Empty(context->ctx()); + return __kraken_invoke_module__(context, moduleName, method, empty, nullptr, exception); +} + +AtomicString ModuleManager::__kraken_invoke_module__(ExecutingContext* context, + const AtomicString& moduleName, + const AtomicString& method, + ScriptValue& paramsValue, + ExceptionState& exception) { + return __kraken_invoke_module__(context, moduleName, method, paramsValue, nullptr, exception); +} + +AtomicString ModuleManager::__kraken_invoke_module__(ExecutingContext* context, + const AtomicString& moduleName, + const AtomicString& method, + ScriptValue& paramsValue, + std::shared_ptr<QJSFunction> callback, + ExceptionState& exception) { + std::unique_ptr<NativeString> params; + if (!paramsValue.IsEmpty()) { + params = paramsValue.ToJSONStringify(&exception).ToString().ToNativeString(); + if (exception.HasException()) { + return AtomicString::Empty(context->ctx()); + } + } + + if (context->dartMethodPtr()->invokeModule == nullptr) { + exception.ThrowException( + context->ctx(), ErrorType::InternalError, + "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); + return AtomicString::Empty(context->ctx()); + } + + auto moduleCallback = ModuleCallback::Create(callback); + context->ModuleCallbacks()->AddModuleCallbacks(std::move(moduleCallback)); + ModuleContext* moduleContext = new ModuleContext{context, moduleCallback}; + + NativeString* result; + if (callback != nullptr) { + result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), + moduleName.ToNativeString().get(), method.ToNativeString().get(), + params.get(), handleInvokeModuleTransientCallback); + } else { + result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), + moduleName.ToNativeString().get(), method.ToNativeString().get(), + params.get(), handleInvokeModuleUnexpectedCallback); + } + + if (result == nullptr) { + return AtomicString::Empty(context->ctx()); + } + + return AtomicString::From(context->ctx(), result); +} + +void ModuleManager::__kraken_add_module_listener__(ExecutingContext* context, + const std::shared_ptr<QJSFunction>& handler, + ExceptionState& exception) { + auto listener = ModuleListener::Create(handler); + context->ModuleListeners()->AddModuleListener(listener); +} + +} // namespace kraken diff --git a/bridge/core/frame/module_manager.d.ts b/bridge/core/frame/module_manager.d.ts new file mode 100644 index 0000000000..d8bf9eaebb --- /dev/null +++ b/bridge/core/frame/module_manager.d.ts @@ -0,0 +1,2 @@ +declare const __kraken_invoke_module__: (moduleName: string, methodName: string, paramsValue?: any, callback?: Function) => string; +declare const __kraken_add_module_listener__: (callback: Function) => void; diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h new file mode 100644 index 0000000000..360b5c896f --- /dev/null +++ b/bridge/core/frame/module_manager.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MODULE_MANAGER_H +#define KRAKENBRIDGE_MODULE_MANAGER_H + +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/qjs_function.h" +#include "module_callback.h" + +namespace kraken { + +class ModuleManager { + public: + static AtomicString __kraken_invoke_module__(ExecutingContext* context, + const AtomicString& moduleName, + const AtomicString& method, + ExceptionState& exception); + static AtomicString __kraken_invoke_module__(ExecutingContext* context, + const AtomicString& moduleName, + const AtomicString& method, + ScriptValue& params, + ExceptionState& exception); + static AtomicString __kraken_invoke_module__(ExecutingContext* context, + const AtomicString& moduleName, + const AtomicString& method, + ScriptValue& params, + std::shared_ptr<QJSFunction> callback, + ExceptionState& exception); + static void __kraken_add_module_listener__(ExecutingContext* context, + const std::shared_ptr<QJSFunction>& handler, + ExceptionState& exception); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_MODULE_MANAGER_H diff --git a/bridge/bindings/qjs/module_manager_test.cc b/bridge/core/frame/module_manager_test.cc similarity index 63% rename from bridge/bindings/qjs/module_manager_test.cc rename to bridge/core/frame/module_manager_test.cc index f64fbf370b..b5e7d09dd2 100644 --- a/bridge/bindings/qjs/module_manager_test.cc +++ b/bridge/core/frame/module_manager_test.cc @@ -3,12 +3,34 @@ */ #include <gtest/gtest.h> -#include "executing_context.h" -#include "host_object.h" #include "kraken_test_env.h" -#include "page.h" -namespace kraken::binding::qjs { +namespace kraken { + +TEST(ModuleManager, ShouldReturnCorrectValue) { + bool static errorCalled = false; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + + auto context = bridge->GetExecutingContext(); + + std::string code = std::string(R"( +let object = { + key: { + v: { + a: { + other: null + } + } + } +}; +let result = kraken.methodChannel.invokeMethod('abc', 'fn', object); +console.log(result); +)"); + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} TEST(ModuleManager, shouldThrowErrorWhenBadJSON) { bool static errorCalled = false; @@ -19,7 +41,7 @@ TEST(ModuleManager, shouldThrowErrorWhenBadJSON) { }); kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); std::string code = std::string(R"( let object = { @@ -34,7 +56,7 @@ let object = { object.other = object; kraken.methodChannel.invokeMethod('abc', 'fn', object); )"); - context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(errorCalled, true); } @@ -51,7 +73,7 @@ TEST(ModuleManager, invokeModuleError) { "'}"); }; - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); std::string code = std::string(R"( function f() { @@ -65,9 +87,9 @@ function f() { } f(); )"); - context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(logCalled, true); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/core/frame/screen.cc b/bridge/core/frame/screen.cc new file mode 100644 index 0000000000..278fb039a7 --- /dev/null +++ b/bridge/core/frame/screen.cc @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "screen.h" +#include "core/frame/window.h" +#include "foundation/native_value_converter.h" + +namespace kraken { + +Screen::Screen(Window* window, NativeBindingObject* native_binding_object) + : EventTargetWithInlineData(window->GetExecutingContext()) { + BindDartObject(native_binding_object); +} + +} // namespace kraken diff --git a/bridge/core/frame/screen.d.ts b/bridge/core/frame/screen.d.ts new file mode 100644 index 0000000000..0f2478771b --- /dev/null +++ b/bridge/core/frame/screen.d.ts @@ -0,0 +1,10 @@ +import {EventTarget} from "../dom/events/event_target"; + +export interface Screen extends EventTarget { + readonly availWidth: DartImpl<double>; + readonly availHeight: DartImpl<int64>; + readonly width: DartImpl<int64>; + readonly height: DartImpl<int64>; + + new(): void; +} diff --git a/bridge/core/frame/screen.h b/bridge/core/frame/screen.h new file mode 100644 index 0000000000..aee755fe78 --- /dev/null +++ b/bridge/core/frame/screen.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_SCREEN_H +#define KRAKENBRIDGE_SCREEN_H + +#include "core/dom/events/event_target.h" + +namespace kraken { + +class Window; + +struct NativeScreen {}; + +class Screen : public EventTargetWithInlineData { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = Screen*; + explicit Screen(Window* window, NativeBindingObject* binding_object); + + private: +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_SCREEN_H diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc new file mode 100644 index 0000000000..5854703375 --- /dev/null +++ b/bridge/core/frame/window.cc @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "window.h" +#include "binding_call_methods.h" +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "core/dom/document.h" +#include "core/events/message_event.h" +#include "event_type_names.h" +#include "foundation/native_value_converter.h" + +namespace kraken { + +Window::Window(ExecutingContext* context) : EventTargetWithInlineData(context) { + context->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateWindow, (void*)bindingObject()); +} + +Window* Window::open(ExceptionState& exception_state) { + return this; +} + +Window* Window::open(const AtomicString& url, ExceptionState& exception_state) { + const NativeValue args[] = { + NativeValueConverter<NativeTypeString>::ToNativeValue(url.ToNativeString().release()), + }; + InvokeBindingMethod(binding_call_methods::kopen, 1, args, exception_state); +} + +Screen* Window::screen() { + if (screen_ == nullptr) { + NativeValue value = GetBindingProperty(binding_call_methods::kscreen, ASSERT_NO_EXCEPTION()); + screen_ = MakeGarbageCollected<Screen>( + this, NativeValueConverter<NativeTypePointer<NativeBindingObject>>::FromNativeValue(value)); + } + return screen_; +} + +void Window::scroll(ExceptionState& exception_state) { + return scroll(0, 0, exception_state); +} + +void Window::scroll(double x, double y, ExceptionState& exception_state) { + const NativeValue args[] = { + NativeValueConverter<NativeTypeDouble>::ToNativeValue(x), + NativeValueConverter<NativeTypeDouble>::ToNativeValue(y), + }; + InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); +} + +void Window::scroll(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state) { + const NativeValue args[] = { + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->left()), + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->top()), + }; + InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); +} + +void Window::scrollBy(ExceptionState& exception_state) { + return scrollBy(0, 0, exception_state); +} + +void Window::scrollBy(double x, double y, ExceptionState& exception_state) { + const NativeValue args[] = { + NativeValueConverter<NativeTypeDouble>::ToNativeValue(x), + NativeValueConverter<NativeTypeDouble>::ToNativeValue(y), + }; + InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, exception_state); +} + +void Window::scrollBy(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state) { + const NativeValue args[] = { + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->left()), + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->top()), + }; + InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, exception_state); +} + +void Window::scrollTo(ExceptionState& exception_state) { + return scroll(exception_state); +} + +void Window::scrollTo(double x, double y, ExceptionState& exception_state) { + return scroll(x, y, exception_state); +} + +void Window::scrollTo(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state) { + return scroll(options, exception_state); +} + +void Window::postMessage(const ScriptValue& message, ExceptionState& exception_state) { + auto event_init = MessageEventInit::Create(); + event_init->setData(message); + auto* message_event = + MessageEvent::Create(GetExecutingContext(), event_type_names::kmessage, event_init, exception_state); + dispatchEvent(message_event, exception_state); +} + +void Window::postMessage(const ScriptValue& message, + const AtomicString& target_origin, + ExceptionState& exception_state) { + auto event_init = MessageEventInit::Create(); + event_init->setData(message); + event_init->setOrigin(target_origin); + auto* message_event = + MessageEvent::Create(GetExecutingContext(), event_type_names::kmessage, event_init, exception_state); + dispatchEvent(message_event, exception_state); +} + +double Window::requestAnimationFrame(const std::shared_ptr<QJSFunction>& callback, ExceptionState& exceptionState) { + if (GetExecutingContext()->dartMethodPtr()->flushUICommand == nullptr) { + exceptionState.ThrowException(ctx(), ErrorType::InternalError, + "Failed to execute 'flushUICommand': dart method (flushUICommand) executed " + "with unexpected error."); + return 0; + } + + GetExecutingContext()->FlushUICommand(); + auto frame_callback = FrameCallback::Create(GetExecutingContext(), callback); + uint32_t request_id = GetExecutingContext()->document()->RequestAnimationFrame(frame_callback, exceptionState); + // `-1` represents some error occurred. + if (request_id == -1) { + exceptionState.ThrowException( + ctx(), ErrorType::InternalError, + "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) executed " + "with unexpected error."); + return 0; + } + return request_id; +} + +void Window::cancelAnimationFrame(double request_id, ExceptionState& exception_state) { + GetExecutingContext()->document()->CancelAnimationFrame(static_cast<uint32_t>(request_id), exception_state); +} + +void Window::Trace(GCVisitor* visitor) const { + visitor->Trace(screen_); + EventTargetWithInlineData::Trace(visitor); +} + +} // namespace kraken diff --git a/bridge/core/frame/window.d.ts b/bridge/core/frame/window.d.ts new file mode 100644 index 0000000000..a6a0294c7f --- /dev/null +++ b/bridge/core/frame/window.d.ts @@ -0,0 +1,32 @@ +import {EventTarget} from "../dom/events/event_target"; +import {ScrollOptions} from "../dom/scroll_options"; +import {ScrollToOptions} from "../dom/scroll_to_options"; +import {Screen} from "./screen"; + +interface Window extends EventTarget { + open(url?: string): Window | null; + scrollTo(options?: ScrollToOptions): void; + scrollTo(x: number, y: number): void; + scrollBy(options?: ScrollToOptions): void; + scrollBy(x: number, y: number): void; + + postMessage(message: any, targetOrigin: string): void; + postMessage(message: any): void; + + requestAnimationFrame(callback: Function): double; + cancelAnimationFrame(request_id: double): void; + + readonly window: Window; + readonly parent: Window; + readonly self: Window; + readonly screen: Screen; + + readonly scrollX: DartImpl<double>; + readonly scrollY: DartImpl<double>; + readonly devicePixelRatio: DartImpl<double>; + readonly colorScheme: DartImpl<string>; + readonly innerWidth: DartImpl<double>; + readonly innerHeight: DartImpl<double>; + + new(): void; +} diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h new file mode 100644 index 0000000000..437632adca --- /dev/null +++ b/bridge/core/frame/window.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_WINDOW_H +#define KRAKENBRIDGE_WINDOW_H + +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/wrapper_type_info.h" +#include "core/dom/events/event_target.h" +#include "qjs_scroll_to_options.h" +#include "screen.h" + +namespace kraken { + +class Window : public EventTargetWithInlineData { + DEFINE_WRAPPERTYPEINFO(); + + public: + Window() = delete; + Window(ExecutingContext* context); + + Window* open(ExceptionState& exception_state); + Window* open(const AtomicString& url, ExceptionState& exception_state); + + Screen* screen(); + + [[nodiscard]] const Window* window() const { return this; } + [[nodiscard]] const Window* self() const { return this; } + [[nodiscard]] const Window* parent() const { return this; } + + void scroll(ExceptionState& exception_state); + void scroll(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state); + void scroll(double x, double y, ExceptionState& exception_state); + void scrollTo(ExceptionState& exception_state); + void scrollTo(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state); + void scrollTo(double x, double y, ExceptionState& exception_state); + void scrollBy(ExceptionState& exception_state); + void scrollBy(double x, double y, ExceptionState& exception_state); + void scrollBy(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state); + + void postMessage(const ScriptValue& message, ExceptionState& exception_state); + void postMessage(const ScriptValue& message, const AtomicString& target_origin, ExceptionState& exception_state); + + double requestAnimationFrame(const std::shared_ptr<QJSFunction>& callback, ExceptionState& exceptionState); + void cancelAnimationFrame(double request_id, ExceptionState& exception_state); + + void Trace(GCVisitor* visitor) const override; + + private: + Member<Screen> screen_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_WINDOW_H diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc new file mode 100644 index 0000000000..35ce9fdb53 --- /dev/null +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "window_or_worker_global_scope.h" +#include "core/frame/dom_timer.h" + +namespace kraken { + +static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { + auto* context = timer->context(); + + if (errmsg != nullptr) { + JSValue exception = JS_ThrowTypeError(context->ctx(), "%s", errmsg); + context->HandleException(&exception); + return; + } + + if (context->Timers()->getTimerById(timer->timerId()) == nullptr) + return; + + // Trigger timer callbacks. + timer->Fire(); + + // Executing pending async jobs. + context->DrainPendingPromiseJobs(); +} + +static void handleTransientCallback(void* ptr, int32_t contextId, const char* errmsg) { + auto* timer = static_cast<DOMTimer*>(ptr); + auto* context = timer->context(); + + if (!context->IsValid()) + return; + + timer->SetStatus(DOMTimer::TimerStatus::kExecuting); + handleTimerCallback(timer, errmsg); + timer->SetStatus(DOMTimer::TimerStatus::kFinished); + + context->Timers()->removeTimeoutById(timer->timerId()); +} + +static void handlePersistentCallback(void* ptr, int32_t contextId, const char* errmsg) { + auto* timer = static_cast<DOMTimer*>(ptr); + auto* context = timer->context(); + + if (!context->IsValid()) + return; + + handleTimerCallback(timer, errmsg); +} + +int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, + std::shared_ptr<QJSFunction> handler, + ExceptionState& exception) { + return setTimeout(context, handler, 0.0, exception); +} + +int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, + std::shared_ptr<QJSFunction> handler, + int32_t timeout, + ExceptionState& exception) { +#if FLUTTER_BACKEND + if (context->dartMethodPtr()->setTimeout == nullptr) { + exception.ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'setTimeout': dart method (setTimeout) is not registered."); + return -1; + } +#endif + + // Create a timer object to keep track timer callback. + auto timer = DOMTimer::create(context, handler); + auto timerId = + context->dartMethodPtr()->setTimeout(timer.get(), context->contextId(), handleTransientCallback, timeout); + + // Register timerId. + timer->setTimerId(timerId); + + context->Timers()->installNewTimer(context, timerId, timer); + + return timerId; +} + +int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, + std::shared_ptr<QJSFunction> handler, + ExceptionState& exception) { + return setInterval(context, handler, 0.0, exception); +} + +int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, + std::shared_ptr<QJSFunction> handler, + int32_t timeout, + ExceptionState& exception) { + if (context->dartMethodPtr()->setInterval == nullptr) { + exception.ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'setInterval': dart method (setInterval) is not registered."); + return -1; + } + + // Create a timer object to keep track timer callback. + auto timer = DOMTimer::create(context, handler); + + uint32_t timerId = + context->dartMethodPtr()->setInterval(timer.get(), context->contextId(), handlePersistentCallback, timeout); + + // Register timerId. + timer->setTimerId(timerId); + context->Timers()->installNewTimer(context, timerId, timer); + + return timerId; +} + +void WindowOrWorkerGlobalScope::clearTimeout(ExecutingContext* context, int32_t timerId, ExceptionState& exception) { + if (context->dartMethodPtr()->clearTimeout == nullptr) { + exception.ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'clearTimeout': dart method (clearTimeout) is not registered."); + return; + } + + context->dartMethodPtr()->clearTimeout(context->contextId(), timerId); + + context->Timers()->removeTimeoutById(timerId); +} + +} // namespace kraken diff --git a/bridge/core/frame/window_or_worker_global_scope.d.ts b/bridge/core/frame/window_or_worker_global_scope.d.ts new file mode 100644 index 0000000000..83c71d3f35 --- /dev/null +++ b/bridge/core/frame/window_or_worker_global_scope.d.ts @@ -0,0 +1,10 @@ +// @ts-ignore +declare const setTimeout: (callback: Function, timeout?: double) => int64; + +// @ts-ignore +declare const setInterval: (callback: Function, timeout?: double) => int64; + +// @ts-ignore +declare const clearTimeout: (handle: double) => void; + + diff --git a/bridge/core/frame/window_or_worker_global_scope.h b/bridge/core/frame/window_or_worker_global_scope.h new file mode 100644 index 0000000000..657f061755 --- /dev/null +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H +#define KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H + +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/qjs_function.h" +#include "core/executing_context.h" + +namespace kraken { + +class WindowOrWorkerGlobalScope { + public: + static int setTimeout(ExecutingContext* context, + std::shared_ptr<QJSFunction> handler, + int32_t timeout, + ExceptionState& exception); + static int setTimeout(ExecutingContext* context, std::shared_ptr<QJSFunction> handler, ExceptionState& exception); + static int setInterval(ExecutingContext* context, + std::shared_ptr<QJSFunction> handler, + int32_t timeout, + ExceptionState& exception); + static int setInterval(ExecutingContext* context, std::shared_ptr<QJSFunction> handler, ExceptionState& exception); + static void clearTimeout(ExecutingContext* context, int32_t timerId, ExceptionState& exception); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H diff --git a/bridge/bindings/qjs/bom/window_test.cc b/bridge/core/frame/window_test.cc similarity index 64% rename from bridge/bindings/qjs/bom/window_test.cc rename to bridge/core/frame/window_test.cc index cbc25fdf00..cc3a4c85bc 100644 --- a/bridge/bindings/qjs/bom/window_test.cc +++ b/bridge/core/frame/window_test.cc @@ -5,7 +5,27 @@ #include "window.h" #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "page.h" + +using namespace kraken; + +TEST(Window, windowIsGlobalThis) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "true"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = "console.log(window === globalThis)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} TEST(Window, instanceofEventTarget) { bool static errorCalled = false; @@ -18,7 +38,7 @@ TEST(Window, instanceofEventTarget) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "console.log(window instanceof EventTarget)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); @@ -28,8 +48,12 @@ TEST(Window, instanceofEventTarget) { TEST(Window, requestAnimationFrame) { auto bridge = TEST_init(); + bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { EXPECT_STREQ(message.c_str(), "456"); }; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "456"); + logCalled = true; + }; std::string code = R"( requestAnimationFrame(() => { @@ -38,7 +62,9 @@ requestAnimationFrame(() => { )"; bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - TEST_runLoop(bridge->getContext()); + TEST_runLoop(bridge->GetExecutingContext()); + + EXPECT_EQ(logCalled, true); } TEST(Window, cancelAnimationFrame) { @@ -47,14 +73,14 @@ TEST(Window, cancelAnimationFrame) { kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { abort(); }; std::string code = R"( -let id = requestAnimationFrame(() => { + let id = requestAnimationFrame(() => { console.log('456'); }); -cancelAnimationFrame(id); + cancelAnimationFrame(id); )"; bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - TEST_runLoop(bridge->getContext()); + TEST_runLoop(bridge->GetExecutingContext()); } TEST(Window, postMessage) { @@ -63,14 +89,14 @@ TEST(Window, postMessage) { static bool logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; - EXPECT_STREQ(message.c_str(), "{\"data\":1234} "); + EXPECT_STREQ(message.c_str(), "{\"data\":1234} *"); }; std::string code = std::string(R"( -window.onmessage = (message) => { + window.addEventListener('message', (message) => { console.log(JSON.stringify(message.data), message.origin); -}; -window.postMessage({ +}); + window.postMessage({ data: 1234 }, '*'); )"); @@ -86,11 +112,11 @@ TEST(Window, location) { static bool logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; - EXPECT_STREQ(message.c_str(), "true true"); + EXPECT_STREQ(message.c_str(), "true true true"); }; std::string code = std::string(R"( - console.log(window.location !== undefined, window.location === document.location); + console.log(window.location !== undefined, window.location === location, window.location === document.location); )"); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(logCalled, true); diff --git a/bridge/core/html/canvas/html_canvas_element.cc b/bridge/core/html/canvas/html_canvas_element.cc new file mode 100644 index 0000000000..751a45642c --- /dev/null +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_canvas_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLCanvasElement::HTMLCanvasElement(Document& document) : HTMLElement(html_names::kcanvas, &document) {} +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/elements/canvas_element.d.ts b/bridge/core/html/canvas/html_canvas_element.d.ts similarity index 79% rename from bridge/bindings/qjs/dom/elements/canvas_element.d.ts rename to bridge/core/html/canvas/html_canvas_element.d.ts index dcb0cd8083..0bd9f91131 100644 --- a/bridge/bindings/qjs/dom/elements/canvas_element.d.ts +++ b/bridge/core/html/canvas/html_canvas_element.d.ts @@ -1,10 +1,6 @@ -type int64 = number; -type double = number; +import {HTMLElement} from "../html_element"; -interface HostObject {} -interface Element {} - -interface CanvasRenderingContext2D extends HostObject { +interface CanvasRenderingContext2D { fillStyle: string; direction: string; font: string; @@ -25,9 +21,9 @@ interface CanvasRenderingContext2D extends HostObject { clearRect(x: number, y: number, w: number, h: number): void; closePath(): void; clip(path?: string): void; - drawImage(image: CanvasImageSource, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void; - drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void; - drawImage(image: CanvasImageSource, dx: number, dy: number): void; + drawImage(image: HTMLImageElement, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void; + drawImage(image: HTMLImageElement, dx: number, dy: number, dw: number, dh: number): void; + drawImage(image: HTMLImageElement, dx: number, dy: number): void; ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void; fill(path?: string): void; fillRect(x: number, y: number, w: number, h: number): void; @@ -50,7 +46,7 @@ interface CanvasRenderingContext2D extends HostObject { reset(): void; } -interface CanvasElement extends Element { +interface HTMLCanvasElement extends HTMLElement { width: int64; height: int64; getContext: (contextType: string) => CanvasRenderingContext2D; diff --git a/bridge/core/html/canvas/html_canvas_element.h b/bridge/core/html/canvas/html_canvas_element.h new file mode 100644 index 0000000000..a1731a0790 --- /dev/null +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ + +#include "core/html/html_element.h" + +namespace kraken { + +class HTMLCanvasElement : public HTMLElement { + public: + explicit HTMLCanvasElement(Document&); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_button_element.cc b/bridge/core/html/forms/html_button_element.cc new file mode 100644 index 0000000000..e19c6343eb --- /dev/null +++ b/bridge/core/html/forms/html_button_element.cc @@ -0,0 +1,5 @@ +// +// Created by yhtree on 2022/4/15. +// + +#include "html_button_element.h" diff --git a/bridge/core/html/forms/html_button_element.h b/bridge/core/html/forms/html_button_element.h new file mode 100644 index 0000000000..4a5f5bb516 --- /dev/null +++ b/bridge/core/html/forms/html_button_element.h @@ -0,0 +1,10 @@ +// +// Created by yhtree on 2022/4/15. +// + +#ifndef KRAKENBRIDGE_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ + +class html_button_element {}; + +#endif // KRAKENBRIDGE_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_input_element.cc b/bridge/core/html/forms/html_input_element.cc new file mode 100644 index 0000000000..b854bd64ee --- /dev/null +++ b/bridge/core/html/forms/html_input_element.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_input_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLInputElement::HTMLInputElement(Document& document) : HTMLElement(html_names::kinput, &document) {} + +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/elements/input_element.d.ts b/bridge/core/html/forms/html_input_element.d.ts similarity index 83% rename from bridge/bindings/qjs/dom/elements/input_element.d.ts rename to bridge/core/html/forms/html_input_element.d.ts index 09484b99b6..5a3117a410 100644 --- a/bridge/bindings/qjs/dom/elements/input_element.d.ts +++ b/bridge/core/html/forms/html_input_element.d.ts @@ -1,7 +1,6 @@ -interface HostObject {} -interface Element {} +import {HTMLElement} from "../html_element"; -interface InputElement extends Element { +interface HTMLInputElement extends HTMLElement { width: number; height: number; defaultValue: string; diff --git a/bridge/core/html/forms/html_input_element.h b/bridge/core/html/forms/html_input_element.h new file mode 100644 index 0000000000..e83c6e4355 --- /dev/null +++ b/bridge/core/html/forms/html_input_element.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ + +#include "core/html/html_element.h" + +namespace kraken { + +class HTMLInputElement : public HTMLElement { + public: + explicit HTMLInputElement(Document&); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_textarea_element.cc b/bridge/core/html/forms/html_textarea_element.cc new file mode 100644 index 0000000000..6c9523d5ec --- /dev/null +++ b/bridge/core/html/forms/html_textarea_element.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_textarea_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLTextareaElement::HTMLTextareaElement(Document& document) : HTMLElement(html_names::ktextarea, &document) {} + +} // namespace kraken diff --git a/bridge/core/html/forms/html_textarea_element.d.ts b/bridge/core/html/forms/html_textarea_element.d.ts new file mode 100644 index 0000000000..15a84ca12b --- /dev/null +++ b/bridge/core/html/forms/html_textarea_element.d.ts @@ -0,0 +1,22 @@ +// https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element +import {HTMLElement} from "../html_element"; + +interface HTMLTextAreaElement extends HTMLElement { + defaultValue: string; + value: string; + cols: double; + rows: double; + wrap: string; + autofocus: boolean; + autocomplete: string; + disabled: boolean; + minLength: double; + maxLength: double; + name: string; + placeholder: string; + readonly: boolean; + required: boolean; + inputMode: string; + focus(): void; + blur(): void; +} diff --git a/bridge/core/html/forms/html_textarea_element.h b/bridge/core/html/forms/html_textarea_element.h new file mode 100644 index 0000000000..b985795c48 --- /dev/null +++ b/bridge/core/html/forms/html_textarea_element.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ + +#include "core/html/html_element.h" + +namespace kraken { + +class HTMLTextareaElement : public HTMLElement { + public: + explicit HTMLTextareaElement(Document&); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ diff --git a/bridge/core/html/html_all_collection.cc b/bridge/core/html/html_all_collection.cc new file mode 100644 index 0000000000..3fb06081ff --- /dev/null +++ b/bridge/core/html/html_all_collection.cc @@ -0,0 +1,80 @@ +///* +// * Copyright (C) 2021 Alibaba Inc. All rights reserved. +// * Author: Kraken Team. +// */ +// +//#include "html_all_collection.h" +// +// namespace kraken { +// +// JSValue AllCollection::item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// if (argc < 1) { +// return JS_NULL; +// } +// +// uint32_t index; +// JS_ToUint32(ctx, &index, argv[0]); +// auto* collection = static_cast<AllCollection*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// +// if (index >= collection->m_nodes.size()) { +// return JS_NULL; +// } +// +// auto node = collection->m_nodes[index]; +// return node->jsObject; +//} +// JSValue AllCollection::add(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// if (argc < 1) { +// return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: 1 arguments required."); +// } +// +// if (!JS_IsObject(argv[0])) { +// return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: first arguments should be a +// object."); +// } +// +// JSValue before = JS_NULL; +// +// if (argc == 2 && JS_IsObject(argv[1])) { +// before = argv[1]; +// } +// +// auto* node = static_cast<NodeInstance*>(JS_GetOpaque(argv[0], ExecutionContext::kHostObjectClassId)); +// auto* collection = static_cast<AllCollection*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// NodeInstance* beforeNode = nullptr; +// +// if (!JS_IsNull(before)) { +// beforeNode = static_cast<NodeInstance*>(JS_GetOpaque(before, ExecutionContext::kHostObjectClassId)); +// } +// +// collection->internalAdd(node, beforeNode); +// +// return JS_NULL; +//} +// JSValue AllCollection::remove(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// if (argc < 1) { +// return JS_ThrowTypeError(ctx, "Failed to execute remove() on HTMLAllCollection: 1 arguments required."); +// } +// +// uint32_t index; +// JS_ToUint32(ctx, &index, argv[0]); +// auto* collection = static_cast<AllCollection*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// collection->m_nodes.erase(collection->m_nodes.begin() + index); +// return JS_NULL; +//} +// void AllCollection::internalAdd(NodeInstance* node, NodeInstance* before) { +// if (before != nullptr) { +// auto it = std::find(m_nodes.begin(), m_nodes.end(), before); +// m_nodes.erase(it); +// m_nodes.insert(it, node); +// } else { +// m_nodes.emplace_back(node); +// } +//} +// +// IMPL_PROPERTY_GETTER(AllCollection, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* collection = static_cast<AllCollection*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// return JS_NewUint32(ctx, collection->m_nodes.size()); +//} +// +//} // namespace kraken diff --git a/bridge/core/html/html_all_collection.h b/bridge/core/html/html_all_collection.h new file mode 100644 index 0000000000..9483d30fd0 --- /dev/null +++ b/bridge/core/html/html_all_collection.h @@ -0,0 +1,31 @@ +///* +// * Copyright (C) 2021 Alibaba Inc. All rights reserved. +// * Author: Kraken Team. +// */ +// +//#ifndef KRAKENBRIDGE_HTML_ALL_COLLECTION_H +//#define KRAKENBRIDGE_HTML_ALL_COLLECTION_H +// +//#include "bindings/qjs/garbage_collected.h" +// +// namespace kraken { +// +// class HTMLAllCollection : public HostObject { +// public: +// AllCollection(ExecutionContext* context) : HostObject(context, "AllCollection"){}; +// +// static JSValue item(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); +// static JSValue add(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); +// static JSValue remove(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); +// +// DEFINE_READONLY_PROPERTY(length); +// +// void internalAdd(NodeInstance* node, NodeInstance* before); +// +// private: +// std::vector<NodeInstance*> m_nodes; +//}; +// +//} // namespace kraken +// +//#endif // KRAKENBRIDGE_HTML_ALL_COLLECTION_H diff --git a/bridge/core/html/html_anchor_element.cc b/bridge/core/html/html_anchor_element.cc new file mode 100644 index 0000000000..f13705e0bf --- /dev/null +++ b/bridge/core/html/html_anchor_element.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_anchor_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLAnchorElement::HTMLAnchorElement(Document& document) : HTMLElement(html_names::ka, &document) {} + +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/elements/anchor_element.d.ts b/bridge/core/html/html_anchor_element.d.ts similarity index 85% rename from bridge/bindings/qjs/dom/elements/anchor_element.d.ts rename to bridge/core/html/html_anchor_element.d.ts index 95a3a5b239..9b45b80a9f 100644 --- a/bridge/bindings/qjs/dom/elements/anchor_element.d.ts +++ b/bridge/core/html/html_anchor_element.d.ts @@ -1,5 +1,4 @@ -interface HostObject {} -interface Element {} +import {Element} from "../dom/element"; interface AnchorElement extends Element { href: string; diff --git a/bridge/core/html/html_anchor_element.h b/bridge/core/html/html_anchor_element.h new file mode 100644 index 0000000000..40e2c5c9d3 --- /dev/null +++ b/bridge/core/html/html_anchor_element.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H +#define KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H + +#include "html_element.h" + +namespace kraken { + +class HTMLAnchorElement : public HTMLElement { + public: + explicit HTMLAnchorElement(Document&); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H diff --git a/bridge/core/html/html_body_element.cc b/bridge/core/html/html_body_element.cc new file mode 100644 index 0000000000..d543f07f0a --- /dev/null +++ b/bridge/core/html/html_body_element.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_body_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLBodyElement::HTMLBodyElement(Document& document) : HTMLElement(html_names::kbody, &document) {} + +} // namespace kraken diff --git a/bridge/core/html/html_body_element.d.ts b/bridge/core/html/html_body_element.d.ts new file mode 100644 index 0000000000..b2b9c4ca94 --- /dev/null +++ b/bridge/core/html/html_body_element.d.ts @@ -0,0 +1,5 @@ +import {HTMLElement} from "./html_element"; + +export interface HTMLBodyElement extends HTMLElement { + new(): void; +} diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h new file mode 100644 index 0000000000..da08da4383 --- /dev/null +++ b/bridge/core/html/html_body_element.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ + +#include "html_element.h" + +namespace kraken { + +class HTMLBodyElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = HTMLBodyElement*; + explicit HTMLBodyElement(Document&); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ diff --git a/bridge/core/html/html_collection.cc b/bridge/core/html/html_collection.cc new file mode 100644 index 0000000000..c4e421a942 --- /dev/null +++ b/bridge/core/html/html_collection.cc @@ -0,0 +1,7 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "html_collection.h" + +namespace kraken {} diff --git a/bridge/core/html/html_collection.h b/bridge/core/html/html_collection.h new file mode 100644 index 0000000000..638f65c2b4 --- /dev/null +++ b/bridge/core/html/html_collection.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_COLLECTION_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_COLLECTION_H_ + +#include "bindings/qjs/script_wrappable.h" + +namespace kraken { + +class HTMLCollection : public ScriptWrappable { + public: + private: +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_COLLECTION_H_ diff --git a/bridge/core/html/html_div_element.cc b/bridge/core/html/html_div_element.cc new file mode 100644 index 0000000000..b43750987d --- /dev/null +++ b/bridge/core/html/html_div_element.cc @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "html_div_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLDivElement::HTMLDivElement(Document& document) : HTMLElement(html_names::kdiv, &document) {} + +} // namespace kraken diff --git a/bridge/core/html/html_div_element.d.ts b/bridge/core/html/html_div_element.d.ts new file mode 100644 index 0000000000..2edd591451 --- /dev/null +++ b/bridge/core/html/html_div_element.d.ts @@ -0,0 +1,5 @@ +import {HTMLElement} from "./html_element"; + +export interface HTMLDivElement extends HTMLElement { + new(): void; +} diff --git a/bridge/core/html/html_div_element.h b/bridge/core/html/html_div_element.h new file mode 100644 index 0000000000..f09ffc7629 --- /dev/null +++ b/bridge/core/html/html_div_element.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ + +#include "html_element.h" + +namespace kraken { + +class HTMLDivElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = HTMLDivElement*; + explicit HTMLDivElement(Document&); + + private: +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ diff --git a/bridge/core/html/html_element.cc b/bridge/core/html/html_element.cc new file mode 100644 index 0000000000..7fb68b04aa --- /dev/null +++ b/bridge/core/html/html_element.cc @@ -0,0 +1,7 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "html_element.h" + +namespace kraken {} // namespace kraken diff --git a/bridge/core/html/html_element.d.ts b/bridge/core/html/html_element.d.ts new file mode 100644 index 0000000000..7865df7552 --- /dev/null +++ b/bridge/core/html/html_element.d.ts @@ -0,0 +1,5 @@ +import {Element} from "../dom/element"; + +export interface HTMLElement extends Element { + new(): void; +} diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h new file mode 100644 index 0000000000..e3549ff9ea --- /dev/null +++ b/bridge/core/html/html_element.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ + +#include "core/dom/element.h" + +namespace kraken { + +class HTMLElement : public Element { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = HTMLElement*; + HTMLElement(const AtomicString& tag_name, Document* document, ConstructionType); + + private: +}; + +inline HTMLElement::HTMLElement(const AtomicString& tag_name, + Document* document, + ConstructionType type = kCreateHTMLElement) + : Element(tag_name, document, type) {} + +template <typename T> +bool IsElementOfType(const HTMLElement&); +template <> +inline bool IsElementOfType<const HTMLElement>(const HTMLElement&) { + return true; +} +template <> +inline bool IsElementOfType<const HTMLElement>(const Node& node) { + return IsA<HTMLElement>(node); +} +template <> +struct DowncastTraits<HTMLElement> { + static bool AllowFrom(const Node& node) { return node.IsHTMLElement(); } +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ diff --git a/bridge/core/html/html_head_element.cc b/bridge/core/html/html_head_element.cc new file mode 100644 index 0000000000..1014e0dbc2 --- /dev/null +++ b/bridge/core/html/html_head_element.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_head_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLHeadElement::HTMLHeadElement(Document& document) : HTMLElement(html_names::khead, &document) {} + +} // namespace kraken diff --git a/bridge/core/html/html_head_element.d.ts b/bridge/core/html/html_head_element.d.ts new file mode 100644 index 0000000000..0affb27eb1 --- /dev/null +++ b/bridge/core/html/html_head_element.d.ts @@ -0,0 +1,5 @@ +import {HTMLElement} from "./html_element"; + +export interface HTMLHeadElement extends HTMLElement { + new(): void; +} diff --git a/bridge/core/html/html_head_element.h b/bridge/core/html/html_head_element.h new file mode 100644 index 0000000000..a1f6ae6e52 --- /dev/null +++ b/bridge/core/html/html_head_element.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ + +#include "html_element.h" + +namespace kraken { + +class HTMLHeadElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = HTMLHeadElement*; + explicit HTMLHeadElement(Document&); + + private: +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ diff --git a/bridge/core/html/html_html_element.cc b/bridge/core/html/html_html_element.cc new file mode 100644 index 0000000000..2fb735034e --- /dev/null +++ b/bridge/core/html/html_html_element.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_html_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLHtmlElement::HTMLHtmlElement(Document& document) : HTMLElement(html_names::khtml, &document) {} + +} // namespace kraken diff --git a/bridge/core/html/html_html_element.d.ts b/bridge/core/html/html_html_element.d.ts new file mode 100644 index 0000000000..f7cadf942e --- /dev/null +++ b/bridge/core/html/html_html_element.d.ts @@ -0,0 +1,5 @@ +import {HTMLElement} from "./html_element"; + +export interface HTMLHtmlElement extends HTMLElement { + new(): void; +} diff --git a/bridge/core/html/html_html_element.h b/bridge/core/html/html_html_element.h new file mode 100644 index 0000000000..41ca13ab2a --- /dev/null +++ b/bridge/core/html/html_html_element.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ + +#include "html_element.h" + +namespace kraken { + +class HTMLHtmlElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = HTMLHtmlElement*; + explicit HTMLHtmlElement(Document&); + + private: +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc new file mode 100644 index 0000000000..ad1792d841 --- /dev/null +++ b/bridge/core/html/html_image_element.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_image_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLImageElement::HTMLImageElement(Document& document) : HTMLElement(html_names::kimg, &document) {} + +} // namespace kraken diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h new file mode 100644 index 0000000000..bf03c34813 --- /dev/null +++ b/bridge/core/html/html_image_element.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ + +#include "html_element.h" + +namespace kraken { + +class HTMLImageElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLImageElement(Document& document); + + private: +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ diff --git a/bridge/core/html/html_script_element.cc b/bridge/core/html/html_script_element.cc new file mode 100644 index 0000000000..63e62b9044 --- /dev/null +++ b/bridge/core/html/html_script_element.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_script_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLScriptElement::HTMLScriptElement(Document& document) : HTMLElement(html_names::kscript, &document) {} + +} // namespace kraken diff --git a/bridge/core/html/html_script_element.h b/bridge/core/html/html_script_element.h new file mode 100644 index 0000000000..6998bff6c7 --- /dev/null +++ b/bridge/core/html/html_script_element.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ + +#include "html_element.h" + +namespace kraken { + +class HTMLScriptElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLScriptElement(Document& document); + + private: +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ diff --git a/bridge/core/html/html_tag_names.json5 b/bridge/core/html/html_tag_names.json5 new file mode 100644 index 0000000000..bfdf8ff4aa --- /dev/null +++ b/bridge/core/html/html_tag_names.json5 @@ -0,0 +1,49 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "html_names" + }, + { + "template": "element_factory", + "filename": "html_element_factory" + }, + { + "template": "element_type_helper", + "filename": "html_element_type_helper" + } + ] + }, + "data": [ +// { +// "name": "canvas", +// "interfaceHeaderDir": "core/html/canvas" +// }, +// { +// "name": "a", +// "interfaceName": "HTMLAnchorElement", +// "filename": "html_anchor_element" +// }, + "html", + "body", + "head", + "div", +// { +// "name": "input", +// "interfaceHeaderDir": "core/html/forms" +// }, +// { +// "name": "textarea", +// "interfaceName": "HTMLTextareaElement", +// "interfaceHeaderDir": "core/html/forms" +// }, + "template", +// { +// "name": "img", +// "interfaceName": "HTMLImageElement", +// "filename": "html_image_element" +// }, +// "script" + ] +} diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc new file mode 100644 index 0000000000..20c893e200 --- /dev/null +++ b/bridge/core/html/html_template_element.cc @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_template_element.h" +#include "core/dom/document_fragment.h" +#include "html_names.h" + +namespace kraken { + +HTMLTemplateElement::HTMLTemplateElement(Document& document) : HTMLElement(html_names::ktemplate, &document) {} + +DocumentFragment* HTMLTemplateElement::content() const { + return ContentInternal(); +} + +DocumentFragment* HTMLTemplateElement::ContentInternal() const { + if (!content_ && GetExecutingContext()) + content_ = DocumentFragment::Create(GetDocument()); + + return content_.Get(); +} + +} // namespace kraken diff --git a/bridge/core/html/html_template_element.d.ts b/bridge/core/html/html_template_element.d.ts new file mode 100644 index 0000000000..02c2837ee8 --- /dev/null +++ b/bridge/core/html/html_template_element.d.ts @@ -0,0 +1,7 @@ +import {HTMLElement} from "./html_element"; +import {DocumentFragment} from "../dom/document_fragment"; + +export interface HTMLTemplateElement extends HTMLElement { + readonly content: DocumentFragment; + new(): void; +} diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h new file mode 100644 index 0000000000..3673758e61 --- /dev/null +++ b/bridge/core/html/html_template_element.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_HTML_TEMPLATE_ELEMENT_H +#define KRAKENBRIDGE_HTML_TEMPLATE_ELEMENT_H + +#include "html_element.h" + +namespace kraken { + +class DocumentFragment; + +class HTMLTemplateElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLTemplateElement(Document& document); + + DocumentFragment* content() const; + + private: + DocumentFragment* ContentInternal() const; + mutable Member<DocumentFragment> content_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_TEMPLATE_ELEMENTT_H diff --git a/bridge/bindings/qjs/dom/elements/textarea_element.d.ts b/bridge/core/html/html_text_area_element.d.ts similarity index 67% rename from bridge/bindings/qjs/dom/elements/textarea_element.d.ts rename to bridge/core/html/html_text_area_element.d.ts index a318a5df47..05798d6a38 100644 --- a/bridge/bindings/qjs/dom/elements/textarea_element.d.ts +++ b/bridge/core/html/html_text_area_element.d.ts @@ -1,18 +1,17 @@ -interface HostObject {} -interface Element {} +import {Element} from "../dom/element"; // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element -interface TextareaElement extends Element { +interface HTMLTextAreaElement extends Element { defaultValue: string; value: string; - cols: long; - rows: long; + cols: double; + rows: double; wrap: string; autofocus: boolean; autocomplete: string; disabled: boolean; - minLength: long; - maxLength: long; + minLength: double; + maxLength: double; name: string; placeholder: string; readonly: boolean; diff --git a/bridge/core/html/html_unknown_element.cc b/bridge/core/html/html_unknown_element.cc new file mode 100644 index 0000000000..708d82558f --- /dev/null +++ b/bridge/core/html/html_unknown_element.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_unknown_element.h" + +namespace kraken { + +HTMLUnknownElement::HTMLUnknownElement(const AtomicString& tag_name, Document& document) + : HTMLElement(tag_name, &document) {} + +} // namespace kraken diff --git a/bridge/core/html/html_unknown_element.d.ts b/bridge/core/html/html_unknown_element.d.ts new file mode 100644 index 0000000000..f80b4a9947 --- /dev/null +++ b/bridge/core/html/html_unknown_element.d.ts @@ -0,0 +1,5 @@ +import {HTMLElement} from "./html_element"; + +export interface HTMLUnknownElement extends HTMLElement { + new(): void; +} diff --git a/bridge/core/html/html_unknown_element.h b/bridge/core/html/html_unknown_element.h new file mode 100644 index 0000000000..f3cd657b3f --- /dev/null +++ b/bridge/core/html/html_unknown_element.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ + +#include "core/html/html_element.h" + +namespace kraken { + +class HTMLUnknownElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLUnknownElement(const AtomicString&, Document& document); + + private: +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ diff --git a/bridge/core/html/media/html_audio_element.cc b/bridge/core/html/media/html_audio_element.cc new file mode 100644 index 0000000000..5200d779e5 --- /dev/null +++ b/bridge/core/html/media/html_audio_element.cc @@ -0,0 +1,5 @@ +// +// Created by yhtree on 2022/4/15. +// + +#include "html_audio_element.h" diff --git a/bridge/core/html/media/html_audio_element.h b/bridge/core/html/media/html_audio_element.h new file mode 100644 index 0000000000..7dde1096c9 --- /dev/null +++ b/bridge/core/html/media/html_audio_element.h @@ -0,0 +1,10 @@ +// +// Created by yhtree on 2022/4/15. +// + +#ifndef KRAKENBRIDGE_CORE_HTML_MEDIA_HTML_AUDIO_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_MEDIA_HTML_AUDIO_ELEMENT_H_ + +class html_audio_element {}; + +#endif // KRAKENBRIDGE_CORE_HTML_MEDIA_HTML_AUDIO_ELEMENT_H_ diff --git a/bridge/core/html/parser/html_parser.cc b/bridge/core/html/parser/html_parser.cc new file mode 100644 index 0000000000..7bde226c92 --- /dev/null +++ b/bridge/core/html/parser/html_parser.cc @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include <utility> + +#include "core/dom/document.h" +#include "core/dom/element.h" +#include "foundation/logging.h" +#include "html_parser.h" + +namespace kraken { + +inline std::string trim(const std::string& str) { + std::string tmp = str; + tmp.erase(0, tmp.find_first_not_of(' ')); // prefixing spaces + tmp.erase(tmp.find_last_not_of(' ') + 1); // surfixing spaces + return tmp; +} + +// Parse html,isHTMLFragment should be false if need to automatically complete html, head, and body when they are +// missing. +GumboOutput* parse(const std::string& html, bool isHTMLFragment = false) { + // Gumbo-parser parse HTML. + GumboOutput* htmlTree = gumbo_parse_with_options(&kGumboDefaultOptions, html.c_str(), html.length()); + + if (isHTMLFragment) { + // Find body. + const GumboVector* children = &htmlTree->root->v.element.children; + for (int i = 0; i < children->length; ++i) { + auto* child = (GumboNode*)children->data[i]; + if (child->type == GUMBO_NODE_ELEMENT) { + std::string tagName; + if (child->v.element.tag != GUMBO_TAG_UNKNOWN) { + tagName = gumbo_normalized_tagname(child->v.element.tag); + } else { + GumboStringPiece piece = child->v.element.original_tag; + gumbo_tag_from_original_text(&piece); + tagName = std::string(piece.data, piece.length); + } + + if (tagName.compare("body") == 0) { + htmlTree->root = child; + break; + } + } + } + } + + return htmlTree; +} + +void HTMLParser::traverseHTML(Node* root_node, GumboNode* node) { + auto* context = root_node->GetExecutingContext(); + JSContext* ctx = root_node->GetExecutingContext()->ctx(); + + const GumboVector* children = &node->v.element.children; + for (int i = 0; i < children->length; ++i) { + auto* child = (GumboNode*)children->data[i]; + + if (auto* root_container = DynamicTo<ContainerNode>(root_node)) { + if (child->type == GUMBO_NODE_ELEMENT) { + std::string tagName; + if (child->v.element.tag != GUMBO_TAG_UNKNOWN) { + tagName = gumbo_normalized_tagname(child->v.element.tag); + } else { + GumboStringPiece piece = child->v.element.original_tag; + gumbo_tag_from_original_text(&piece); + tagName = std::string(piece.data, piece.length); + } + + auto* element = context->document()->createElement(AtomicString(ctx, tagName), ASSERT_NO_EXCEPTION()); + root_container->AppendChild(element); + parseProperty(element, &child->v.element); + + // eval javascript when <script>//code...</script>. + if (child->v.element.children.length > 0) { + if (child->v.element.tag == GUMBO_TAG_SCRIPT) { + const char* code = ((GumboNode*)child->v.element.children.data[0])->v.text.text; + context->EvaluateJavaScript(code, strlen(code), "vm://", 0); + } else { + traverseHTML(element, child); + } + } + } else if (child->type == GUMBO_NODE_TEXT) { + auto* text = context->document()->createTextNode(AtomicString(ctx, child->v.text.text), ASSERT_NO_EXCEPTION()); + root_container->AppendChild(text); + } + } + } +} + +bool HTMLParser::parseHTML(const std::string& html, Node* root_node, bool isHTMLFragment) { + if (root_node != nullptr) { + if (auto* root_container_node = DynamicTo<ContainerNode>(root_node)) { + root_container_node->RemoveChildren(); + + if (!trim(html).empty()) { + GumboOutput* htmlTree = parse(html, isHTMLFragment); + traverseHTML(root_container_node, htmlTree->root); + // Free gumbo parse nodes. + gumbo_destroy_output(&kGumboDefaultOptions, htmlTree); + } + } + } else { + KRAKEN_LOG(ERROR) << "Root node is null."; + } + + return true; +} + +bool HTMLParser::parseHTML(const std::string& html, Node* root_node) { + return parseHTML(html, root_node, false); +} + +bool HTMLParser::parseHTML(const char* code, size_t codeLength, Node* root_node) { + std::string html = std::string(code, codeLength); + return parseHTML(html, root_node, false); +} + +bool HTMLParser::parseHTMLFragment(const char* code, size_t codeLength, Node* rootNode) { + std::string html = std::string(code, codeLength); + return parseHTML(html, rootNode, true); +} + +void HTMLParser::parseProperty(Element* element, GumboElement* gumboElement) { + auto* context = element->GetExecutingContext(); + JSContext* ctx = context->ctx(); + + GumboVector* attributes = &gumboElement->attributes; + for (int j = 0; j < attributes->length; ++j) { + auto* attribute = (GumboAttribute*)attributes->data[j]; + + if (strcmp(attribute->name, "style") == 0) { + std::vector<std::string> arrStyles; + std::string::size_type prev_pos = 0, pos = 0; + std::string strStyles = attribute->value; + + while ((pos = strStyles.find(';', pos)) != std::string::npos) { + arrStyles.push_back(strStyles.substr(prev_pos, pos - prev_pos)); + prev_pos = ++pos; + } + arrStyles.push_back(strStyles.substr(prev_pos, pos - prev_pos)); + + auto* style = element->style(); + + for (auto& s : arrStyles) { + std::string::size_type position = s.find(':'); + if (position != std::basic_string<char>::npos) { + std::string styleKey = s.substr(0, position); + trim(styleKey); + std::string styleValue = s.substr(position + 1, s.length()); + trim(styleValue); + style->setProperty(AtomicString(ctx, styleKey), AtomicString(ctx, styleValue), ASSERT_NO_EXCEPTION()); + } + } + + } else { + std::string strName = attribute->name; + std::string strValue = attribute->value; + element->setAttribute(AtomicString(ctx, strName), AtomicString(ctx, strValue), ASSERT_NO_EXCEPTION()); + } + } +} + +} // namespace kraken diff --git a/bridge/core/html/parser/html_parser.h b/bridge/core/html/parser/html_parser.h new file mode 100644 index 0000000000..629e4c9f63 --- /dev/null +++ b/bridge/core/html/parser/html_parser.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_HTML_PARSER_H +#define KRAKENBRIDGE_HTML_PARSER_H + +#include <third_party/gumbo-parser/src/gumbo.h> +#include <string> +#include "foundation/native_string.h" + +namespace kraken { + +class Node; +class Element; +class ExecutingContext; + +class HTMLParser { + public: + static bool parseHTML(const char* code, size_t codeLength, Node* rootNode); + static bool parseHTML(const std::string& html, Node* rootNode); + static bool parseHTMLFragment(const char* code, size_t codeLength, Node* rootNode); + + private: + ExecutingContext* context_; + static void traverseHTML(Node* root, GumboNode* node); + static void parseProperty(Element* element, GumboElement* gumboElement); + + static bool parseHTML(const std::string& html, Node* rootNode, bool isHTMLFragment); +}; +} // namespace kraken + +#endif // KRAKENBRIDGE_HTML_PARSER_H diff --git a/bridge/core/page.cc b/bridge/core/page.cc new file mode 100644 index 0000000000..366b955122 --- /dev/null +++ b/bridge/core/page.cc @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include <atomic> +#include <unordered_map> + +#include "bindings/qjs/binding_initializer.h" +#include "core/dart_methods.h" +#include "core/dom/document.h" +#include "core/frame/window.h" +#include "core/html/parser/html_parser.h" +#include "foundation/logging.h" +#include "page.h" +#include "polyfill.h" + +namespace kraken { + +ConsoleMessageHandler KrakenPage::consoleMessageHandler{nullptr}; + +kraken::KrakenPage** KrakenPage::pageContextPool{nullptr}; + +KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) + : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { + context_ = new ExecutingContext( + contextId, + [](ExecutingContext* context, const char* message) { + if (context->dartMethodPtr()->onJsError != nullptr) { + context->dartMethodPtr()->onJsError(context->contextId(), message); + } + KRAKEN_LOG(ERROR) << message << std::endl; + }, + this); +} + +bool KrakenPage::parseHTML(const char* code, size_t length) { + if (!context_->IsValid()) + return false; + + MemberMutationScope scope{context_}; + + // Remove all Nodes including body and head. + context_->document()->documentElement()->RemoveChildren(); + + HTMLParser::parseHTML(code, length, context_->document()->documentElement()); + + return true; +} + +void KrakenPage::invokeModuleEvent(const NativeString* moduleName, + const char* eventType, + void* ptr, + NativeString* extra) { + if (!context_->IsValid()) + return; + + JSContext* ctx = context_->ctx(); + Event* event = nullptr; + if (ptr != nullptr) { + std::string type = std::string(eventType); + auto* rawEvent = static_cast<RawEvent*>(ptr)->bytes; + event = Event::From(context_, reinterpret_cast<NativeEvent*>(rawEvent)); + } + + ScriptValue extraObject = ScriptValue::Empty(ctx); + if (extra != nullptr) { + std::u16string u16Extra = std::u16string(reinterpret_cast<const char16_t*>(extra->string()), extra->length()); + std::string extraString = toUTF8(u16Extra); + extraObject = ScriptValue::CreateJsonObject(ctx, extraString.c_str(), extraString.length()); + } + + auto* listeners = context_->ModuleCallbacks()->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); + } + } +} + +void KrakenPage::evaluateScript(const NativeString* script, const char* url, int startLine) { + if (!context_->IsValid()) + return; + +#if ENABLE_PROFILE + auto nativePerformance = Performance::instance(context_)->m_nativePerformance; + nativePerformance.mark(PERF_JS_PARSE_TIME_START); + std::u16string patchedCode = std::u16string(u"performance.mark('js_parse_time_end');") + + std::u16string(reinterpret_cast<const char16_t*>(script->string), script->length); + context_->evaluateJavaScript(patchedCode.c_str(), patchedCode.size(), url, startLine); +#else + context_->EvaluateJavaScript(script->string(), script->length(), url, startLine); +#endif +} + +void KrakenPage::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { + if (!context_->IsValid()) + return; + context_->EvaluateJavaScript(script, length, url, startLine); +} + +void KrakenPage::evaluateScript(const char* script, size_t length, const char* url, int startLine) { + if (!context_->IsValid()) + return; + context_->EvaluateJavaScript(script, length, url, startLine); +} + +uint8_t* KrakenPage::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { + if (!context_->IsValid()) + return nullptr; + return context_->DumpByteCode(script, length, url, byteLength); +} + +void KrakenPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { + if (!context_->IsValid()) + return; + context_->EvaluateByteCode(bytes, byteLength); +} + +void KrakenPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { + size_t i = 0; + + auto& dartMethodPointer = context_->dartMethodPtr(); + + dartMethodPointer->invokeModule = reinterpret_cast<InvokeModule>(methodBytes[i++]); + dartMethodPointer->requestBatchUpdate = reinterpret_cast<RequestBatchUpdate>(methodBytes[i++]); + dartMethodPointer->reloadApp = reinterpret_cast<ReloadApp>(methodBytes[i++]); + dartMethodPointer->setTimeout = reinterpret_cast<SetTimeout>(methodBytes[i++]); + dartMethodPointer->setInterval = reinterpret_cast<SetInterval>(methodBytes[i++]); + dartMethodPointer->clearTimeout = reinterpret_cast<ClearTimeout>(methodBytes[i++]); + dartMethodPointer->requestAnimationFrame = reinterpret_cast<RequestAnimationFrame>(methodBytes[i++]); + dartMethodPointer->cancelAnimationFrame = reinterpret_cast<CancelAnimationFrame>(methodBytes[i++]); + dartMethodPointer->toBlob = reinterpret_cast<ToBlob>(methodBytes[i++]); + dartMethodPointer->flushUICommand = reinterpret_cast<FlushUICommand>(methodBytes[i++]); + +#if ENABLE_PROFILE + methodPointer->getPerformanceEntries = reinterpret_cast<GetPerformanceEntries>(methodBytes[i++]); +#else + i++; +#endif + + dartMethodPointer->onJsError = reinterpret_cast<OnJSError>(methodBytes[i++]); + dartMethodPointer->onJsLog = reinterpret_cast<OnJSLog>(methodBytes[i++]); + + assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); +} + +std::thread::id KrakenPage::currentThread() const { + return ownerThreadId; +} + +KrakenPage::~KrakenPage() { +#if IS_TEST + if (disposeCallback != nullptr) { + disposeCallback(this); + } +#endif + delete context_; + KrakenPage::pageContextPool[contextId] = nullptr; +} + +void KrakenPage::reportError(const char* errmsg) { + handler_(context_, errmsg); +} + +} // namespace kraken diff --git a/bridge/page.h b/bridge/core/page.h similarity index 59% rename from bridge/page.h rename to bridge/core/page.h index 4619d0ee2e..8923b75688 100644 --- a/bridge/page.h +++ b/bridge/core/page.h @@ -6,23 +6,25 @@ #define KRAKEN_JS_QJS_BRIDGE_H_ #include <quickjs/quickjs.h> -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/html_parser.h" -#include "include/kraken_bridge.h" - #include <atomic> #include <deque> +#include <thread> #include <vector> +#include "core/executing_context.h" +#include "foundation/native_string.h" + namespace kraken { class KrakenPage; using JSBridgeDisposeCallback = void (*)(KrakenPage* bridge); +using ConsoleMessageHandler = std::function<void(void* ctx, const std::string& message, int logLevel)>; -/// KrakenPage is class which manage all js objects create by <Kraken> flutter widget. -/// Every <Kraken> flutter widgets have a corresponding KrakenPage, and all objects created by JavaScript are stored here, -/// and there is no data sharing between objects between different KrakenPages. -/// It's safe to allocate many KrakenPages at the same times on one thread, but not safe for multi-threads, only one thread can enter to KrakenPage at the same time. +/// KrakenPage is class which manage all js objects Create by <Kraken> flutter widget. +/// Every <Kraken> flutter widgets have a corresponding KrakenPage, and all objects created by JavaScript are stored +/// here, and there is no data sharing between objects between different KrakenPages. It's safe to Allocate many +/// KrakenPages at the same times on one thread, but not safe for multi-threads, only one thread can enter to KrakenPage +/// at the same time. class KrakenPage final { public: static kraken::KrakenPage** pageContextPool; @@ -31,9 +33,6 @@ class KrakenPage final { KrakenPage(int32_t jsContext, const JSExceptionHandler& handler); ~KrakenPage(); - // Bytecodes which registered by kraken plugins. - static std::unordered_map<std::string, NativeByteCode> pluginByteCode; - // evaluate JavaScript source codes in standard mode. void evaluateScript(const NativeString* script, const char* url, int startLine); void evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine); @@ -42,9 +41,12 @@ class KrakenPage final { uint8_t* dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength); void evaluateByteCode(uint8_t* bytes, size_t byteLength); - [[nodiscard]] kraken::binding::qjs::ExecutionContext* getContext() const { return m_context; } + void registerDartMethods(uint64_t* methodBytes, int32_t length); + std::thread::id currentThread() const; + + [[nodiscard]] ExecutingContext* GetExecutingContext() const { return context_; } - void invokeModuleEvent(NativeString* moduleName, const char* eventType, void* event, NativeString* extra); + void invokeModuleEvent(const NativeString* moduleName, const char* eventType, void* event, NativeString* extra); void reportError(const char* errmsg); int32_t contextId; @@ -54,10 +56,12 @@ class KrakenPage final { JSBridgeDisposeCallback disposeCallback{nullptr}; #endif private: - // FIXME: we must to use raw pointer instead of unique_ptr because we needs to access m_context when dispose page. - // TODO: Raw pointer is dangerous and just works but it's fragile. We needs refactor this for more stable and maintainable. - binding::qjs::ExecutionContext* m_context; - JSExceptionHandler m_handler; + const std::thread::id ownerThreadId; + // FIXME: we must to use raw pointer instead of unique_ptr because we needs to access context_ when dispose page. + // TODO: Raw pointer is dangerous and just works but it's fragile. We needs refactor this for more stable and + // maintainable. + ExecutingContext* context_; + JSExceptionHandler handler_; }; } // namespace kraken diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc new file mode 100644 index 0000000000..717f0e841c --- /dev/null +++ b/bridge/core/script_state.cc @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_state.h" +#include "binding_call_methods.h" +#include "built_in_string.h" +#include "event_type_names.h" +#include "html_element_factory.h" +#include "html_names.h" + +namespace kraken { + +JSRuntime* runtime_ = nullptr; +std::atomic<int32_t> runningContexts{0}; + +ScriptState::ScriptState() { + runningContexts++; + bool first_loaded = false; + if (runtime_ == nullptr) { + runtime_ = JS_NewRuntime(); + first_loaded = true; + } + // Avoid stack overflow when running in multiple threads. + JS_UpdateStackTop(runtime_); + ctx_ = JS_NewContext(runtime_); + + if (first_loaded) { + built_in_string::Init(ctx_); + event_type_names::Init(ctx_); + html_names::Init(ctx_); + binding_call_methods::Init(ctx_); + // 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}; + JS_NewClassID(&id); + } + } +} + +JSRuntime* ScriptState::runtime() { + return runtime_; +} + +ScriptState::~ScriptState() { + JS_FreeContext(ctx_); + + // Run GC to clean up remaining objects about m_ctx; + JS_RunGC(runtime_); + +#if DUMP_LEAKS + if (--runningContexts == 0) { + // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. + built_in_string::Dispose(); + event_type_names::Dispose(); + html_names::Dispose(); + binding_call_methods::Dispose(); + HTMLElementFactory::Dispose(); + + JS_FreeRuntime(runtime_); + runtime_ = nullptr; + } +#endif + ctx_ = nullptr; +} +} // namespace kraken diff --git a/bridge/core/script_state.h b/bridge/core/script_state.h new file mode 100644 index 0000000000..e3b7f407a2 --- /dev/null +++ b/bridge/core/script_state.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_SCRIPT_STATE_H_ +#define KRAKENBRIDGE_CORE_SCRIPT_STATE_H_ + +#include <quickjs/quickjs.h> +#include "bindings/qjs/script_wrappable.h" + +namespace kraken { + +// ScriptState is an abstraction class that holds all information about script +// execution (e.g., JSContext etc). If you need any info about the script execution, you're expected to +// pass around ScriptState in the code base. ScriptState is in a 1:1 +// relationship with JSContext. +class ScriptState { + public: + ScriptState(); + ~ScriptState(); + + inline JSContext* ctx() { return ctx_; } + static JSRuntime* runtime(); + + private: + JSContext* ctx_{nullptr}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_SCRIPT_STATE_H_ diff --git a/bridge/bindings/qjs/bom/performance.cc b/bridge/core/timing/performance.cc similarity index 82% rename from bridge/bindings/qjs/bom/performance.cc rename to bridge/core/timing/performance.cc index ef32ba004a..be9d30e07d 100644 --- a/bridge/bindings/qjs/bom/performance.cc +++ b/bridge/core/timing/performance.cc @@ -8,7 +8,7 @@ #define PERFORMANCE_ENTRY_NONE_UNIQUE_ID -1024 -namespace kraken::binding::qjs { +namespace kraken { void bindPerformance(ExecutionContext* context) { auto* performance = Performance::instance(context); @@ -39,7 +39,9 @@ IMPL_PROPERTY_GETTER(PerformanceEntry, duration)(JSContext* ctx, JSValue this_va IMPL_PROPERTY_GETTER(Performance, timeOrigin)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - int64_t time = std::chrono::duration_cast<std::chrono::milliseconds>(performance->m_context->timeOrigin.time_since_epoch()).count(); + int64_t time = + std::chrono::duration_cast<std::chrono::milliseconds>(performance->m_context->timeOrigin.time_since_epoch()) + .count(); return JS_NewUint32(ctx, time); } @@ -50,7 +52,9 @@ JSValue Performance::now(JSContext* ctx, JSValue this_val, int argc, JSValue* ar JSValue Performance::toJSON(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); double now = performance->internalNow(); - int64_t timeOrigin = std::chrono::duration_cast<std::chrono::milliseconds>(performance->m_context->timeOrigin.time_since_epoch()).count(); + int64_t timeOrigin = + std::chrono::duration_cast<std::chrono::milliseconds>(performance->m_context->timeOrigin.time_since_epoch()) + .count(); JSValue object = JS_NewObject(ctx); JS_SetPropertyStr(ctx, object, "now", JS_NewFloat64(ctx, now)); @@ -58,7 +62,9 @@ JSValue Performance::toJSON(JSContext* ctx, JSValue this_val, int argc, JSValue* return object; } -static JSValue buildPerformanceEntry(const std::string& entryType, ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) { +static JSValue buildPerformanceEntry(const std::string& entryType, + ExecutionContext* context, + NativePerformanceEntry* nativePerformanceEntry) { if (entryType == "mark") { auto* mark = new PerformanceMark(context, nativePerformanceEntry); return mark->jsObject; @@ -152,7 +158,8 @@ JSValue Performance::getEntries(JSContext* ctx, JSValue this_val, int argc, JSVa } JSValue Performance::getEntriesByName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc == 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); + return JS_ThrowTypeError( + ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); } std::string targetName = jsValueToStdString(ctx, argv[0]); @@ -174,7 +181,8 @@ JSValue Performance::getEntriesByName(JSContext* ctx, JSValue this_val, int argc } JSValue Performance::getEntriesByType(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc == 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); + return JS_ThrowTypeError( + ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); } std::string entryType = jsValueToStdString(ctx, argv[0]); @@ -195,7 +203,8 @@ JSValue Performance::getEntriesByType(JSContext* ctx, JSValue this_val, int argc } JSValue Performance::mark(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc != 1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'mark' on 'Performance': 1 argument required, but only 0 present."); + return JS_ThrowTypeError(ctx, + "Failed to execute 'mark' on 'Performance': 1 argument required, but only 0 present."); } auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); @@ -206,7 +215,8 @@ JSValue Performance::mark(JSContext* ctx, JSValue this_val, int argc, JSValue* a } JSValue Performance::measure(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc == 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 present."); + return JS_ThrowTypeError(ctx, + "Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 present."); } std::string name = jsValueToStdString(ctx, argv[0]); @@ -237,42 +247,64 @@ PerformanceEntry::PerformanceEntry(ExecutionContext* context, NativePerformanceE : HostObject(context, "PerformanceEntry"), m_nativePerformanceEntry(nativePerformanceEntry) {} PerformanceMark::PerformanceMark(ExecutionContext* context, std::string& name, int64_t startTime) - : PerformanceEntry(context, new NativePerformanceEntry(name, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} -PerformanceMark::PerformanceMark(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) : PerformanceEntry(context, nativePerformanceEntry) {} -PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, std::string& name, int64_t startTime, int64_t duration) - : PerformanceEntry(context, new NativePerformanceEntry(name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} -PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) : PerformanceEntry(context, nativePerformanceEntry) {} + : PerformanceEntry(context, + new NativePerformanceEntry(name, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} +PerformanceMark::PerformanceMark(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) + : PerformanceEntry(context, nativePerformanceEntry) {} +PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, + std::string& name, + int64_t startTime, + int64_t duration) + : PerformanceEntry( + context, + new NativePerformanceEntry(name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} +PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) + : PerformanceEntry(context, nativePerformanceEntry) {} void NativePerformance::mark(const std::string& markName) { int64_t startTime = std::chrono::duration_cast<microseconds>(system_clock::now().time_since_epoch()).count(); - auto* nativePerformanceEntry = new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; + auto* nativePerformanceEntry = + new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; entries->emplace_back(nativePerformanceEntry); } void NativePerformance::mark(const std::string& markName, int64_t startTime) { - auto* nativePerformanceEntry = new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; + auto* nativePerformanceEntry = + new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; entries->emplace_back(nativePerformanceEntry); } Performance::Performance(ExecutionContext* context) : HostObject(context, "Performance") {} -void Performance::internalMeasure(const std::string& name, const std::string& startMark, const std::string& endMark, JSValue* exception) { +void Performance::internalMeasure(const std::string& name, + const std::string& startMark, + const std::string& endMark, + JSValue* exception) { auto entries = getFullEntries(); if (!startMark.empty() && !endMark.empty()) { - size_t startMarkCount = std::count_if(entries.begin(), entries.end(), [&startMark](NativePerformanceEntry* entry) -> bool { return entry->name == startMark; }); + size_t startMarkCount = + std::count_if(entries.begin(), entries.end(), + [&startMark](NativePerformanceEntry* entry) -> bool { return entry->name == startMark; }); if (startMarkCount == 0) { - *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not exist.", startMark.c_str()); + *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not exist.", + startMark.c_str()); return; } - size_t endMarkCount = std::count_if(entries.begin(), entries.end(), [&endMark](NativePerformanceEntry* entry) -> bool { return entry->name == endMark; }); + size_t endMarkCount = + std::count_if(entries.begin(), entries.end(), + [&endMark](NativePerformanceEntry* entry) -> bool { return entry->name == endMark; }); if (endMarkCount == 0) { - *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not exist.", endMark.c_str()); + *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not exist.", + endMark.c_str()); return; } if (startMarkCount != endMarkCount) { - *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s and %s does not appear the same number of times", startMark.c_str(), endMark.c_str()); + *exception = JS_ThrowTypeError( + m_ctx, + "Failed to execute 'measure' on 'Performance': The mark %s and %s does not appear the same number of times", + startMark.c_str(), endMark.c_str()); return; } @@ -280,11 +312,14 @@ void Performance::internalMeasure(const std::string& name, const std::string& st auto endIt = std::begin(entries); for (size_t i = 0; i < startMarkCount; i++) { - auto startEntry = std::find_if(startIt, entries.end(), [&startMark](NativePerformanceEntry* entry) -> bool { return entry->name == startMark; }); + auto startEntry = std::find_if(startIt, entries.end(), [&startMark](NativePerformanceEntry* entry) -> bool { + return entry->name == startMark; + }); bool isStartEntryHasUniqueId = (*startEntry)->uniqueId != PERFORMANCE_ENTRY_NONE_UNIQUE_ID; - auto endEntryComparator = [&endMark, &startEntry, isStartEntryHasUniqueId](NativePerformanceEntry* entry) -> bool { + auto endEntryComparator = [&endMark, &startEntry, + isStartEntryHasUniqueId](NativePerformanceEntry* entry) -> bool { if (isStartEntryHasUniqueId) { return entry->uniqueId == (*startEntry)->uniqueId && entry->name == endMark; } @@ -295,12 +330,14 @@ void Performance::internalMeasure(const std::string& name, const std::string& st if (endEntry == entries.end()) { size_t startIndex = startEntry - entries.begin(); - assert_m(false, ("Can not get endEntry. startIndex: " + std::to_string(startIndex) + " startMark: " + startMark + " endMark: " + endMark)); + assert_m(false, ("Can not get endEntry. startIndex: " + std::to_string(startIndex) + + " startMark: " + startMark + " endMark: " + endMark)); } int64_t duration = (*endEntry)->startTime - (*startEntry)->startTime; int64_t startTime = std::chrono::duration_cast<microseconds>(system_clock::now().time_since_epoch()).count(); - auto* nativePerformanceEntry = new NativePerformanceEntry{name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; + auto* nativePerformanceEntry = + new NativePerformanceEntry{name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; m_nativePerformance.entries->emplace_back(nativePerformanceEntry); startIt = ++startEntry; endIt = ++endEntry; @@ -351,21 +388,31 @@ std::vector<NativePerformanceEntry*> Performance::getFullEntries() { void Performance::measureSummary(JSValue* exception) { internalMeasure(PERF_WIDGET_CREATION_COST, PERF_CONTROLLER_INIT_START, PERF_CONTROLLER_INIT_END, exception); - internalMeasure(PERF_CONTROLLER_PROPERTIES_INIT_COST, PERF_CONTROLLER_INIT_START, PERF_CONTROLLER_PROPERTY_INIT, exception); - internalMeasure(PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST, PERF_VIEW_CONTROLLER_INIT_START, PERF_VIEW_CONTROLLER_PROPERTY_INIT, exception); + internalMeasure(PERF_CONTROLLER_PROPERTIES_INIT_COST, PERF_CONTROLLER_INIT_START, PERF_CONTROLLER_PROPERTY_INIT, + exception); + internalMeasure(PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST, PERF_VIEW_CONTROLLER_INIT_START, + PERF_VIEW_CONTROLLER_PROPERTY_INIT, exception); internalMeasure(PERF_BRIDGE_INIT_COST, PERF_BRIDGE_INIT_START, PERF_BRIDGE_INIT_END, exception); - internalMeasure(PERF_BRIDGE_REGISTER_DART_METHOD_COST, PERF_BRIDGE_REGISTER_DART_METHOD_START, PERF_BRIDGE_REGISTER_DART_METHOD_END, exception); + internalMeasure(PERF_BRIDGE_REGISTER_DART_METHOD_COST, PERF_BRIDGE_REGISTER_DART_METHOD_START, + PERF_BRIDGE_REGISTER_DART_METHOD_END, exception); internalMeasure(PERF_CREATE_VIEWPORT_COST, PERF_CREATE_VIEWPORT_START, PERF_CREATE_VIEWPORT_END, exception); - internalMeasure(PERF_ELEMENT_MANAGER_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, PERF_ELEMENT_MANAGER_INIT_END, exception); - internalMeasure(PERF_ELEMENT_MANAGER_PROPERTIES_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, PERF_ELEMENT_MANAGER_PROPERTY_INIT, exception); + internalMeasure(PERF_ELEMENT_MANAGER_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, PERF_ELEMENT_MANAGER_INIT_END, + exception); + internalMeasure(PERF_ELEMENT_MANAGER_PROPERTIES_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, + PERF_ELEMENT_MANAGER_PROPERTY_INIT, exception); internalMeasure(PERF_ROOT_ELEMENT_INIT_COST, PERF_ROOT_ELEMENT_INIT_START, PERF_ROOT_ELEMENT_INIT_END, exception); - internalMeasure(PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST, PERF_ROOT_ELEMENT_INIT_START, PERF_ROOT_ELEMENT_PROPERTY_INIT, exception); + internalMeasure(PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST, PERF_ROOT_ELEMENT_INIT_START, PERF_ROOT_ELEMENT_PROPERTY_INIT, + exception); internalMeasure(PERF_JS_CONTEXT_INIT_COST, PERF_JS_CONTEXT_INIT_START, PERF_JS_CONTEXT_INIT_END, exception); - internalMeasure(PERF_JS_HOST_CLASS_GET_PROPERTY_COST, PERF_JS_HOST_CLASS_GET_PROPERTY_START, PERF_JS_HOST_CLASS_GET_PROPERTY_END, exception); - internalMeasure(PERF_JS_HOST_CLASS_SET_PROPERTY_COST, PERF_JS_HOST_CLASS_SET_PROPERTY_START, PERF_JS_HOST_CLASS_SET_PROPERTY_END, exception); + internalMeasure(PERF_JS_HOST_CLASS_GET_PROPERTY_COST, PERF_JS_HOST_CLASS_GET_PROPERTY_START, + PERF_JS_HOST_CLASS_GET_PROPERTY_END, exception); + internalMeasure(PERF_JS_HOST_CLASS_SET_PROPERTY_COST, PERF_JS_HOST_CLASS_SET_PROPERTY_START, + PERF_JS_HOST_CLASS_SET_PROPERTY_END, exception); internalMeasure(PERF_JS_HOST_CLASS_INIT_COST, PERF_JS_HOST_CLASS_INIT_START, PERF_JS_HOST_CLASS_INIT_END, exception); - internalMeasure(PERF_JS_NATIVE_FUNCTION_CALL_COST, PERF_JS_NATIVE_FUNCTION_CALL_START, PERF_JS_NATIVE_FUNCTION_CALL_END, exception); - internalMeasure(PERF_JS_NATIVE_METHOD_INIT_COST, PERF_JS_NATIVE_METHOD_INIT_START, PERF_JS_NATIVE_METHOD_INIT_END, exception); + internalMeasure(PERF_JS_NATIVE_FUNCTION_CALL_COST, PERF_JS_NATIVE_FUNCTION_CALL_START, + PERF_JS_NATIVE_FUNCTION_CALL_END, exception); + internalMeasure(PERF_JS_NATIVE_METHOD_INIT_COST, PERF_JS_NATIVE_METHOD_INIT_START, PERF_JS_NATIVE_METHOD_INIT_END, + exception); internalMeasure(PERF_JS_POLYFILL_INIT_COST, PERF_JS_POLYFILL_INIT_START, PERF_JS_POLYFILL_INIT_END, exception); internalMeasure(PERF_JS_BUNDLE_LOAD_COST, PERF_JS_BUNDLE_LOAD_START, PERF_JS_BUNDLE_LOAD_END, exception); internalMeasure(PERF_JS_BUNDLE_EVAL_COST, PERF_JS_BUNDLE_EVAL_START, PERF_JS_BUNDLE_EVAL_END, exception); @@ -373,9 +420,11 @@ void Performance::measureSummary(JSValue* exception) { internalMeasure(PERF_CREATE_ELEMENT_COST, PERF_CREATE_ELEMENT_START, PERF_CREATE_ELEMENT_END, exception); internalMeasure(PERF_CREATE_TEXT_NODE_COST, PERF_CREATE_TEXT_NODE_START, PERF_CREATE_TEXT_NODE_END, exception); internalMeasure(PERF_CREATE_COMMENT_COST, PERF_CREATE_COMMENT_START, PERF_CREATE_COMMENT_END, exception); - internalMeasure(PERF_DISPOSE_EVENT_TARGET_COST, PERF_DISPOSE_EVENT_TARGET_START, PERF_DISPOSE_EVENT_TARGET_END, exception); + internalMeasure(PERF_DISPOSE_EVENT_TARGET_COST, PERF_DISPOSE_EVENT_TARGET_START, PERF_DISPOSE_EVENT_TARGET_END, + exception); internalMeasure(PERF_ADD_EVENT_COST, PERF_ADD_EVENT_START, PERF_ADD_EVENT_END, exception); - internalMeasure(PERF_INSERT_ADJACENT_NODE_COST, PERF_INSERT_ADJACENT_NODE_START, PERF_INSERT_ADJACENT_NODE_END, exception); + internalMeasure(PERF_INSERT_ADJACENT_NODE_COST, PERF_INSERT_ADJACENT_NODE_START, PERF_INSERT_ADJACENT_NODE_END, + exception); internalMeasure(PERF_REMOVE_NODE_COST, PERF_REMOVE_NODE_START, PERF_REMOVE_NODE_END, exception); internalMeasure(PERF_SET_STYLE_COST, PERF_SET_STYLE_START, PERF_SET_STYLE_END, exception); internalMeasure(PERF_SET_PROPERTIES_COST, PERF_SET_PROPERTIES_START, PERF_SET_PROPERTIES_END, exception); @@ -386,11 +435,13 @@ void Performance::measureSummary(JSValue* exception) { internalMeasure(PERF_SILVER_LAYOUT_COST, PERF_SILVER_LAYOUT_START, PERF_SILVER_LAYOUT_END, exception); internalMeasure(PERF_PAINT_COST, PERF_PAINT_START, PERF_PAINT_END, exception); internalMeasure(PERF_DOM_FORCE_LAYOUT_COST, PERF_DOM_FORCE_LAYOUT_START, PERF_DOM_FORCE_LAYOUT_END, exception); - internalMeasure(PERF_DOM_FLUSH_UI_COMMAND_COST, PERF_DOM_FLUSH_UI_COMMAND_START, PERF_DOM_FLUSH_UI_COMMAND_END, exception); + internalMeasure(PERF_DOM_FLUSH_UI_COMMAND_COST, PERF_DOM_FLUSH_UI_COMMAND_START, PERF_DOM_FLUSH_UI_COMMAND_END, + exception); internalMeasure(PERF_JS_PARSE_TIME_COST, PERF_JS_PARSE_TIME_START, PERF_JS_PARSE_TIME_END, exception); } -std::vector<NativePerformanceEntry*> findAllMeasures(const std::vector<NativePerformanceEntry*>& entries, const std::string& targetName) { +std::vector<NativePerformanceEntry*> findAllMeasures(const std::vector<NativePerformanceEntry*>& entries, + const std::string& targetName) { std::vector<NativePerformanceEntry*> resultEntries; for (auto entry : entries) { @@ -473,14 +524,17 @@ JSValue Performance::__kraken_navigation_summary__(JSContext* ctx, JSValue this_ GET_COST(paint, PERF_PAINT_COST); GET_COST(domForceLayout, PERF_DOM_FORCE_LAYOUT_COST); GET_COST(domFlushUICommand, PERF_DOM_FLUSH_UI_COMMAND_COST); - GET_COST_WITH_DECREASE(jsHostClassGetProperty, PERF_JS_HOST_CLASS_GET_PROPERTY_COST, domForceLayoutCost + domFlushUICommandCost) + GET_COST_WITH_DECREASE(jsHostClassGetProperty, PERF_JS_HOST_CLASS_GET_PROPERTY_COST, + domForceLayoutCost + domFlushUICommandCost) GET_COST(jsHostClassSetProperty, PERF_JS_HOST_CLASS_SET_PROPERTY_COST); GET_COST(jsHostClassInit, PERF_JS_HOST_CLASS_INIT_COST); GET_COST(jsNativeFunction, PERF_JS_NATIVE_FUNCTION_CALL_COST); GET_COST_WITH_DECREASE(jsBundleEval, PERF_JS_BUNDLE_EVAL_COST, domForceLayoutCost + domFlushUICommandCost); - double initBundleCost = jsBundleLoadCost + jsBundleEvalCost + flushUiCommandCost + createElementCost + createTextNodeCost + createCommentCost + disposeEventTargetCost + addEventCost + - insertAdjacentNodeCost + removeNodeCost + setStyleCost + setPropertiesCost + removePropertiesCost; + double initBundleCost = jsBundleLoadCost + jsBundleEvalCost + flushUiCommandCost + createElementCost + + createTextNodeCost + createCommentCost + disposeEventTargetCost + addEventCost + + insertAdjacentNodeCost + removeNodeCost + setStyleCost + setPropertiesCost + + removePropertiesCost; // layout and paint measure are not correct. double renderingCost = flexLayoutCost + flowLayoutCost + intrinsicLayoutCost + silverLayoutCost + paintCost; double totalCost = widgetCreationCost + initBundleCost; @@ -579,4 +633,4 @@ Rendering: %.*fms #endif -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/bom/performance.h b/bridge/core/timing/performance.h similarity index 95% rename from bridge/bindings/qjs/bom/performance.h rename to bridge/core/timing/performance.h index 0e7c9ab870..d477d11b9f 100644 --- a/bridge/bindings/qjs/bom/performance.h +++ b/bridge/core/timing/performance.h @@ -122,12 +122,17 @@ #include "bindings/qjs/host_object.h" -namespace kraken::binding::qjs { +namespace kraken { void bindPerformance(ExecutionContext* context); struct NativePerformanceEntry { - NativePerformanceEntry(const std::string& name, const std::string& entryType, int64_t startTime, int64_t duration, int64_t uniqueId) : startTime(startTime), duration(duration), uniqueId(uniqueId) { + NativePerformanceEntry(const std::string& name, + const std::string& entryType, + int64_t startTime, + int64_t duration, + int64_t uniqueId) + : startTime(startTime), duration(duration), uniqueId(uniqueId) { this->name = new char[name.size() + 1]; this->entryType = new char[entryType.size() + 1]; strcpy(this->name, name.data()); @@ -202,7 +207,10 @@ class Performance : public HostObject { DEFINE_READONLY_PROPERTY(timeOrigin); private: - void internalMeasure(const std::string& name, const std::string& startMark, const std::string& endMark, JSValue* exception); + void internalMeasure(const std::string& name, + const std::string& startMark, + const std::string& endMark, + JSValue* exception); double internalNow(); std::vector<NativePerformanceEntry*> getFullEntries(); @@ -221,6 +229,6 @@ class Performance : public HostObject { #endif }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_PERFORMANCE_H diff --git a/bridge/dart_methods.cc b/bridge/dart_methods.cc deleted file mode 100644 index af21efeb41..0000000000 --- a/bridge/dart_methods.cc +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2019-present The Kraken authors. All rights reserved. - */ - -#include "dart_methods.h" -#include <memory> -#include "kraken_bridge.h" - -namespace kraken { - -std::shared_ptr<DartMethodPointer> methodPointer = std::make_shared<DartMethodPointer>(); - -std::shared_ptr<DartMethodPointer> getDartMethod() { - std::thread::id currentThread = std::this_thread::get_id(); - -#ifndef NDEBUG - // Dart methods can only invoked from Flutter UI threads. Javascript Debugger like Safari Debugger can invoke - // Javascript methods from debugger thread and will crash the app. - // @TODO: implement task loops for async method call. - if (currentThread != getUIThreadId()) { - // return empty struct to stop further behavior. - return std::make_shared<DartMethodPointer>(); - } -#endif - - return methodPointer; -} - -void registerDartMethods(uint64_t* methodBytes, int32_t length) { - size_t i = 0; - - methodPointer->invokeModule = reinterpret_cast<InvokeModule>(methodBytes[i++]); - methodPointer->requestBatchUpdate = reinterpret_cast<RequestBatchUpdate>(methodBytes[i++]); - methodPointer->reloadApp = reinterpret_cast<ReloadApp>(methodBytes[i++]); - methodPointer->setTimeout = reinterpret_cast<SetTimeout>(methodBytes[i++]); - methodPointer->setInterval = reinterpret_cast<SetInterval>(methodBytes[i++]); - methodPointer->clearTimeout = reinterpret_cast<ClearTimeout>(methodBytes[i++]); - methodPointer->requestAnimationFrame = reinterpret_cast<RequestAnimationFrame>(methodBytes[i++]); - methodPointer->cancelAnimationFrame = reinterpret_cast<CancelAnimationFrame>(methodBytes[i++]); - methodPointer->getScreen = reinterpret_cast<GetScreen>(methodBytes[i++]); - methodPointer->toBlob = reinterpret_cast<ToBlob>(methodBytes[i++]); - methodPointer->flushUICommand = reinterpret_cast<FlushUICommand>(methodBytes[i++]); - methodPointer->initWindow = reinterpret_cast<InitWindow>(methodBytes[i++]); - methodPointer->initDocument = reinterpret_cast<InitDocument>(methodBytes[i++]); - -#if ENABLE_PROFILE - methodPointer->getPerformanceEntries = reinterpret_cast<GetPerformanceEntries>(methodBytes[i++]); -#else - i++; -#endif - - methodPointer->onJsError = reinterpret_cast<OnJSError>(methodBytes[i++]); - methodPointer->onJsLog = reinterpret_cast<OnJSLog>(methodBytes[i++]); - - assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); -} - -void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length) { - size_t i = 0; - - methodPointer->onJsError = reinterpret_cast<OnJSError>(methodBytes[i++]); - methodPointer->matchImageSnapshot = reinterpret_cast<MatchImageSnapshot>(methodBytes[i++]); - methodPointer->environment = reinterpret_cast<Environment>(methodBytes[i++]); - methodPointer->simulatePointer = reinterpret_cast<SimulatePointer>(methodBytes[i++]); - methodPointer->simulateInputText = reinterpret_cast<SimulateInputText>(methodBytes[i++]); - - assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); -} - -#if ENABLE_PROFILE -void registerGetPerformanceEntries(GetPerformanceEntries getPerformanceEntries) { - methodPointer->getPerformanceEntries = getPerformanceEntries; -} -#endif - -} // namespace kraken diff --git a/bridge/foundation/ascii_types.h b/bridge/foundation/ascii_types.h new file mode 100644 index 0000000000..4242b5e8df --- /dev/null +++ b/bridge/foundation/ascii_types.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ +#define KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ + +namespace kraken { + +template <typename CharType> +inline bool IsASCII(CharType c) { + return !(c & ~0x7F); +} + +template <typename CharType> +inline bool IsASCIIAlpha(CharType c) { + return (c | 0x20) >= 'a' && (c | 0x20) <= 'z'; +} + +template <typename CharType> +inline bool IsASCIIDigit(CharType c) { + return c >= '0' && c <= '9'; +} + +template <typename CharType> +inline bool IsASCIIAlphanumeric(CharType c) { + return IsASCIIDigit(c) || IsASCIIAlpha(c); +} + +/* + Statistics from a run of Apple's page load test for callers of IsASCIISpace: + + character count + --------- ----- + non-spaces 689383 + 20 space 294720 + 0A \n 89059 + 09 \t 28320 + 0D \r 0 + 0C \f 0 + 0B \v 0 + */ +template <typename CharType> +inline bool IsASCIISpace(CharType c) { + return c <= ' ' && (c == ' ' || (c <= 0xD && c >= 0x9)); +} + +template <typename CharType> +inline bool IsASCIIUpper(CharType c) { + return c >= 'A' && c <= 'Z'; +} + +template <typename CharacterType> +inline bool IsLowerASCII(const CharacterType* characters, size_t length) { + bool contains_upper_case = false; + for (size_t i = 0; i < length; i++) { + contains_upper_case |= IsASCIIUpper(characters[i]); + } + return !contains_upper_case; +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ diff --git a/bridge/foundation/casting.h b/bridge/foundation/casting.h new file mode 100644 index 0000000000..603e981266 --- /dev/null +++ b/bridge/foundation/casting.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_FOUNDATION_CASTING_H_ +#define KRAKENBRIDGE_FOUNDATION_CASTING_H_ + +#include <cassert> +#include <type_traits> + +namespace kraken { + +// Helpers for downcasting in a class hierarchy. +// +// IsA<T>(x): returns true if |x| can be safely downcast to T*. Usage of this +// should not be common; if it is paired with a call to To<T>, consider +// using DynamicTo<T> instead (see below). Note that this also returns +// false if |x| is nullptr. +// +// To<T>(x): unconditionally downcasts and returns |x| as a T*. +// Use when IsA<T>(x) is known to be true due to external invariants. +// If |x| is nullptr, returns nullptr. +// +// DynamicTo<T>(x): downcasts and returns |x| as a T* iff IsA<T>(x) is true, +// and nullptr otherwise. This is useful for combining a conditional +// branch on IsA<T>(x) and an invocation of To<T>(x), e.g.: +// if (IsA<DerivedClass>(x)) +// To<DerivedClass>(x)->... +// can be written: +// if (auto* derived = DynamicTo<DerivedClass>(x)) +// derived->...; +// +// Marking downcasts as safe is done by specializing the DowncastTraits +// template: +// +// template <> +// struct DowncastTraits<DerivedClass> { +// static bool AllowFrom(const BaseClass& b) { +// return b.IsDerivedClass(); +// } +// static bool AllowFrom(const AnotherBaseClass& b) { +// return b.type() == AnotherBaseClass::kDerivedClassTag; +// } +// }; +// +// int main() { +// BaseClass* base = CreateDerived(); +// AnotherBaseClass* another_base = CreateDerived(); +// UnrelatedClass* unrelated = CreateUnrelated(); +// +// std::cout << std::boolalpha; +// std::cout << IsA<Derived>(base) << '\n'; // prints true +// std::cout << IsA<Derived>(another_base) << '\n'; // prints true +// std::cout << IsA<Derived>(unrelated) << '\n'; // prints false +// } +template <typename T> +struct DowncastTraits { + template <typename U> + static bool AllowFrom(const U&) { + static_assert(sizeof(U) == 0, "no downcast traits specialization for T"); + assert(false); + return false; + } +}; + +namespace internal { + +// Though redundant with the return type inferred by `auto`, the trailing return +// type is needed for SFINAE. +template <typename Derived, typename Base> +auto IsDowncastAllowedHelper(const Base& from) -> decltype(DowncastTraits<Derived>::AllowFrom(from)) { + return DowncastTraits<Derived>::AllowFrom(from); +} + +} // namespace internal + +// Returns true iff the conversion from Base to Derived is allowed. For the +// pointer overloads, returns false if the input pointer is nullptr. +template <typename Derived, typename Base> +bool IsA(const Base& from) { + return internal::IsDowncastAllowedHelper<Derived>(from); +} + +template <typename Derived, typename Base> +bool IsA(const Base* from) { + return from && IsA<Derived>(*from); +} + +template <typename Derived, typename Base> +bool IsA(Base& from) { + return IsA<Derived>(static_cast<const Base&>(from)); +} + +template <typename Derived, typename Base> +bool IsA(Base* from) { + return from && IsA<Derived>(*from); +} + +// Unconditionally downcasts from Base to Derived. Internally, this asserts +// that |from| is a Derived to help catch bad casts in testing/fuzzing. For the +// pointer overloads, returns nullptr if the input pointer is nullptr. +template <typename Derived, typename Base> +const Derived& To(const Base& from) { + return static_cast<const Derived&>(from); +} + +template <typename Derived, typename Base> +const Derived* To(const Base* from) { + return from ? &To<Derived>(*from) : nullptr; +} + +template <typename Derived, typename Base> +Derived& To(Base& from) { + return static_cast<Derived&>(from); +} +template <typename Derived, typename Base> +Derived* To(Base* from) { + return from ? &To<Derived>(*from) : nullptr; +} + +// Safely downcasts from Base to Derived. If |from| is not a Derived, returns +// nullptr; otherwise, downcasts from Base to Derived. For the pointer +// overloads, returns nullptr if the input pointer is nullptr. +template <typename Derived, typename Base> +const Derived* DynamicTo(const Base* from) { + return IsA<Derived>(from) ? To<Derived>(from) : nullptr; +} + +template <typename Derived, typename Base> +const Derived* DynamicTo(const Base& from) { + return IsA<Derived>(from) ? &To<Derived>(from) : nullptr; +} + +template <typename Derived, typename Base> +Derived* DynamicTo(Base* from) { + return IsA<Derived>(from) ? To<Derived>(from) : nullptr; +} + +template <typename Derived, typename Base> +Derived* DynamicTo(Base& from) { + return IsA<Derived>(from) ? &To<Derived>(from) : nullptr; +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_FOUNDATION_CASTING_H_ diff --git a/bridge/foundation/closure.h b/bridge/foundation/closure.h deleted file mode 100644 index 39fb20b6d6..0000000000 --- a/bridge/foundation/closure.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (C) 2020-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_CLOSURE_H -#define KRAKENBRIDGE_CLOSURE_H - -#include <functional> - -namespace fml { -using closure = std::function<void()>; -} - -#endif // KRAKENBRIDGE_CLOSURE_H diff --git a/bridge/foundation/inspector_task_queue.cc b/bridge/foundation/inspector_task_queue.cc index 3f4af75c5b..f7daa6e9fb 100644 --- a/bridge/foundation/inspector_task_queue.cc +++ b/bridge/foundation/inspector_task_queue.cc @@ -4,9 +4,9 @@ #include "inspector_task_queue.h" -namespace foundation { +namespace kraken { std::mutex InspectorTaskQueue::inspector_task_creation_mutex_{}; fml::RefPtr<InspectorTaskQueue> InspectorTaskQueue::instance_{}; -} // namespace foundation +} // namespace kraken diff --git a/bridge/foundation/inspector_task_queue.h b/bridge/foundation/inspector_task_queue.h index fe0d52ffce..c09c273e4b 100644 --- a/bridge/foundation/inspector_task_queue.h +++ b/bridge/foundation/inspector_task_queue.h @@ -5,10 +5,9 @@ #ifndef KRAKENBRIDGE_INSPECTOR_TASK_QUEUE_H #define KRAKENBRIDGE_INSPECTOR_TASK_QUEUE_H -#include "kraken_foundation.h" #include "task_queue.h" -namespace foundation { +namespace kraken { class InspectorTaskQueue; using Task = void (*)(void*); @@ -25,7 +24,6 @@ class InspectorTaskQueue : public TaskQueue { }; int32_t registerTask(const Task& task, void* data) override { int32_t taskId = TaskQueue::registerTask(task, data); - assert(std::this_thread::get_id() == getUIThreadId()); return taskId; } @@ -35,6 +33,6 @@ class InspectorTaskQueue : public TaskQueue { static fml::RefPtr<InspectorTaskQueue> instance_; }; -} // namespace foundation +} // namespace kraken #endif // KRAKENBRIDGE_INSPECTOR_TASK_QUEUE_H diff --git a/bridge/foundation/logging.cc b/bridge/foundation/logging.cc index cbfd79544e..888465edb8 100644 --- a/bridge/foundation/logging.cc +++ b/bridge/foundation/logging.cc @@ -6,7 +6,7 @@ #include <algorithm> #include "colors.h" -#include "page.h" +#include "core/page.h" #if defined(IS_ANDROID) #include <android/log.h> @@ -23,7 +23,7 @@ #include "inspector/impl/jsc_console_client_impl.h" #endif -namespace foundation { +namespace kraken { namespace { const char* StripDots(const char* path) { @@ -42,7 +42,8 @@ const char* StripPath(const char* path) { } // namespace -LogMessage::LogMessage(LogSeverity severity, const char* file, int line, const char* condition) : severity_(severity), file_(file), line_(line) { +LogMessage::LogMessage(LogSeverity severity, const char* file, int line, const char* condition) + : severity_(severity), file_(file), line_(line) { if (condition) stream_ << "Check failed: " << condition << ". "; } @@ -93,7 +94,7 @@ void pipeMessageToInspector(JSGlobalContextRef ctx, const std::string message, c }; #endif -void printLog(int32_t contextId, std::stringstream& stream, std::string level, void* ctx) { +void printLog(ExecutingContext* context, std::stringstream& stream, std::string level, void* ctx) { MessageLevel _log_level = MessageLevel::Info; switch (level[0]) { case 'l': @@ -124,9 +125,9 @@ void printLog(int32_t contextId, std::stringstream& stream, std::string level, v kraken::KrakenPage::consoleMessageHandler(ctx, stream.str(), static_cast<int>(_log_level)); } - if (kraken::getDartMethod()->onJsLog != nullptr) { - kraken::getDartMethod()->onJsLog(contextId, static_cast<int>(_log_level), stream.str().c_str()); + if (context->dartMethodPtr()->onJsLog != nullptr) { + context->dartMethodPtr()->onJsLog(context->contextId(), static_cast<int>(_log_level), stream.str().c_str()); } } -} // namespace foundation +} // namespace kraken diff --git a/bridge/foundation/logging.h b/bridge/foundation/logging.h index eca7b33d8d..6c4516c66a 100644 --- a/bridge/foundation/logging.h +++ b/bridge/foundation/logging.h @@ -6,11 +6,23 @@ #define KRAKEN_FOUNDATION_LOGGING_H_ #include <sstream> - #include <string> -#include "include/kraken_bridge.h" -namespace foundation { +#define KRAKEN_LOG_STREAM(severity) ::kraken::LogMessage(::kraken::severity, __FILE__, __LINE__, nullptr).stream() + +#define KRAKEN_LAZY_STREAM(stream, condition) !(condition) ? (void)0 : ::kraken::LogMessageVoidify() & (stream) + +#define KRAKEN_EAT_STREAM_PARAMETERS(ignored) \ + true || (ignored) ? (void)0 : ::LogMessageVoidify() & ::LogMessage(::LOG_FATAL, 0, 0, nullptr).stream() + +#define KRAKEN_LOG(severity) KRAKEN_LAZY_STREAM(KRAKEN_LOG_STREAM(severity), true) + +#define KRAKEN_CHECK(condition) \ + KRAKEN_LAZY_STREAM(::kraken::LogMessage(::kraken::FATAL, __FILE__, __LINE__, #condition).stream(), !(condition)) + +namespace kraken { + +class ExecutingContext; typedef int LogSeverity; @@ -20,7 +32,8 @@ constexpr LogSeverity INFO = 1; constexpr LogSeverity WARN = 2; constexpr LogSeverity DEBUG = 3; constexpr LogSeverity ERROR = 4; -constexpr LogSeverity FATAL = 5; +constexpr LogSeverity NUM_SEVERITIES = 5; +constexpr LogSeverity FATAL = 6; enum class MessageLevel : uint8_t { Log = 1, @@ -47,20 +60,10 @@ class LogMessage { const LogSeverity severity_; const char* file_; const int line_; - - KRAKEN_DISALLOW_COPY_AND_ASSIGN(LogMessage); }; -void printLog(int32_t contextId, std::stringstream& stream, std::string level, void* ctx); - -#define KRAKEN_LOG_STREAM(severity) ::foundation::LogMessage(::foundation::severity, __FILE__, __LINE__, nullptr).stream() - -#define KRAKEN_LAZY_STREAM(stream, condition) !(condition) ? (void)0 : foundation::LogMessageVoidify() & (stream) - -#define KRAKEN_LOG(severity) KRAKEN_LAZY_STREAM(KRAKEN_LOG_STREAM(severity), true) - -#define KRAKEN_CHECK(condition) KRAKEN_LAZY_STREAM(::foundation::LogMessage(::foundation::FATAL, __FILE__, __LINE__, #condition).stream(), !(condition)) +void printLog(ExecutingContext* context, std::stringstream& stream, std::string level, void* ctx); -} // namespace foundation +} // namespace kraken #endif // KRAKEN_FOUNDATION_LOGGING_H_ diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h new file mode 100644 index 0000000000..35baeddaeb --- /dev/null +++ b/bridge/foundation/macros.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MACROS_H +#define KRAKENBRIDGE_MACROS_H + +#include <stddef.h> + +#if defined(__GNUC__) || defined(__clang__) +#define LIKELY(x) __builtin_expect(!!(x), 1) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) +#define FORCE_INLINE inline __attribute__((always_inline)) +#else +#define LIKELY(x) (x) +#define UNLIKELY(x) (x) +#define FORCE_INLINE inline +#endif + +#define assert_m(exp, msg) assert(((void)msg, exp)) + +#define KRAKEN_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete + +#define KRAKEN_DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete + +#define KRAKEN_DISALLOW_MOVE(TypeName) \ + TypeName(TypeName&&) = delete; \ + TypeName& operator=(TypeName&&) = delete + +#define KRAKEN_STATIC_ONLY(Type) \ + Type() = delete; \ + Type(const Type&) = delete; \ + Type& operator=(const Type&) = delete; \ + void* operator new(size_t) = delete; \ + void* operator new(size_t, void*) = delete + +#define KRAKEN_STACK_ALLOCATED() \ + private: \ + void* operator new(size_t) = delete; \ + void* operator new(size_t, void*) = delete + +// KRAKEN_DISALLOW_NEW(): Cannot be allocated with new operators but can be a +// part of object, a value object in collections or stack allocated. If it has +// Members you need a trace method and the containing object needs to call that +// trace method. +// +#define KRAKEN_DISALLOW_NEW() \ + public: \ + using IsDisallowNewMarker = int; \ + void* operator new(size_t, void* location) { return location; } \ + \ + private: \ + void* operator new(size_t) = delete; + +#define KRAKEN_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName& operator=(const TypeName&) = delete + +#define KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName(TypeName&&) = delete; \ + TypeName& operator=(const TypeName&) = delete; \ + TypeName& operator=(TypeName&&) = delete + +#define KRAKEN_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) + +#endif // KRAKENBRIDGE_MACROS_H diff --git a/bridge/foundation/native_string.cc b/bridge/foundation/native_string.cc new file mode 100644 index 0000000000..af7d8975c0 --- /dev/null +++ b/bridge/foundation/native_string.cc @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "native_string.h" +#include <string> + +namespace kraken { + +NativeString::NativeString(const uint16_t* string, uint32_t length) : length_(length) { + string_ = static_cast<const uint16_t*>(malloc(length * sizeof(uint16_t))); + memcpy((void*)string_, string, length * sizeof(uint16_t)); +} + +NativeString::NativeString(const NativeString* source) : length_(source->length()) { + string_ = static_cast<const uint16_t*>(malloc(source->length() * sizeof(uint16_t))); + memcpy((void*)string_, source->string_, source->length() * sizeof(u_int16_t)); +} + +NativeString::~NativeString() { + delete[] string_; +} + +} // namespace kraken diff --git a/bridge/foundation/native_string.h b/bridge/foundation/native_string.h new file mode 100644 index 0000000000..024a15c05b --- /dev/null +++ b/bridge/foundation/native_string.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_NATIVE_STRING_H +#define KRAKENBRIDGE_NATIVE_STRING_H + +#include <cinttypes> +#include <cstdlib> +#include <cstring> + +#include "foundation/macros.h" + +namespace kraken { + +struct NativeString { + NativeString(const uint16_t* string, uint32_t length); + NativeString(const NativeString* source); + ~NativeString(); + + inline const uint16_t* string() const { return string_; } + inline uint32_t length() const { return length_; } + + private: + const uint16_t* string_; + uint32_t length_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_NATIVE_STRING_H diff --git a/bridge/foundation/native_type.h b/bridge/foundation/native_type.h new file mode 100644 index 0000000000..49ae7c4542 --- /dev/null +++ b/bridge/foundation/native_type.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ +#define KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ + +#include <type_traits> +#include "bindings/qjs/qjs_function.h" +#include "bindings/qjs/script_value.h" +#include "foundation/native_string.h" + +namespace kraken { + +struct NativeTypeBase { + using ImplType = void; +}; + +template <typename T> +struct NativeTypeBaseHelper { + using ImplType = T; +}; + +// Null +struct NativeTypeNull final : public NativeTypeBaseHelper<ScriptValue> {}; + +// Bool +struct NativeTypeBool final : public NativeTypeBaseHelper<bool> {}; + +// String +struct NativeTypeString final : public NativeTypeBaseHelper<NativeString*> {}; + +// Int64 +struct NativeTypeInt64 final : public NativeTypeBaseHelper<int64_t> {}; + +// Double +struct NativeTypeDouble final : public NativeTypeBaseHelper<double> {}; + +// JSON +struct NativeTypeJSON final : public NativeTypeBaseHelper<ScriptValue> {}; + +// Pointer +template <typename T> +struct NativeTypePointer final : public NativeTypeBaseHelper<T*> {}; + +// Sync function +struct NativeTypeFunction final : public NativeTypeBaseHelper<std::shared_ptr<QJSFunction>> {}; + +// Async function +struct NativeTypeAsyncFunction final : public NativeTypeBaseHelper<std::shared_ptr<QJSFunction>> {}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc new file mode 100644 index 0000000000..60ff30ff70 --- /dev/null +++ b/bridge/foundation/native_value.cc @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "native_value.h" +#include "bindings/qjs/qjs_engine_patch.h" +#include "bindings/qjs/script_value.h" +#include "core/executing_context.h" + +namespace kraken { + +NativeValue Native_NewNull() { + return (NativeValue){.u = {.int64 = 0}, NativeTag::TAG_NULL}; +} + +NativeValue Native_NewString(NativeString* string) { + return (NativeValue){ + .u = {.ptr = static_cast<void*>(string)}, + NativeTag::TAG_STRING, + }; +} + +NativeValue Native_NewCString(std::string string) { + std::unique_ptr<NativeString> nativeString = stringToNativeString(string); + // NativeString owned by NativeValue will be freed by users. + return Native_NewString(nativeString.release()); +} + +NativeValue Native_NewFloat64(double value) { + int64_t result; + memcpy(&result, reinterpret_cast<void*>(&value), sizeof(double)); + + return (NativeValue){ + .u = {.int64 = result}, + NativeTag::TAG_FLOAT64, + }; +} + +NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr) { + return (NativeValue){.u = {.ptr = ptr}, NativeTag::TAG_POINTER}; +} + +NativeValue Native_NewBool(bool value) { + return (NativeValue){ + .u = {.int64 = value ? 1 : 0}, + NativeTag::TAG_BOOL, + }; +} + +NativeValue Native_NewInt64(int64_t value) { + return (NativeValue){ + .u = {.int64 = value}, + NativeTag::TAG_INT, + }; +} + +NativeValue Native_NewJSON(const ScriptValue& value) { + ExceptionState exception_state; + ScriptValue json = value.ToJSONStringify(&exception_state); + if (exception_state.HasException()) { + return Native_NewNull(); + } + + AtomicString str = json.ToString(); + auto native_string = str.ToNativeString(); + NativeValue result = (NativeValue){ + .u = {.ptr = static_cast<void*>(native_string.release())}, + NativeTag::TAG_JSON, + }; + return result; +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/native_value.h b/bridge/foundation/native_value.h similarity index 54% rename from bridge/bindings/qjs/native_value.h rename to bridge/foundation/native_value.h index 1e16ab4882..8a1b6b1726 100644 --- a/bridge/bindings/qjs/native_value.h +++ b/bridge/foundation/native_value.h @@ -5,7 +5,13 @@ #ifndef KRAKENBRIDGE_NATIVE_VALUE_H #define KRAKENBRIDGE_NATIVE_VALUE_H -#include "executing_context.h" +#include <quickjs/list.h> +#include <quickjs/quickjs.h> +#include <cinttypes> +#include <string> +#include "bindings/qjs/native_string_utils.h" + +namespace kraken { enum NativeTag { TAG_STRING = 0, @@ -19,13 +25,19 @@ enum NativeTag { TAG_ASYNC_FUNCTION = 8, }; -enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, NativeBoundingClientRect = 2, NativeCanvasRenderingContext2D = 3, NativeEventTarget = 4 }; +enum class JSPointerType { + AsyncContextContext = 0, + NativeFunctionContext = 1, + NativeBoundingClientRect = 2, + NativeCanvasRenderingContext2D = 3, + BindingObject = 4 +}; -namespace kraken::binding::qjs { +class ExecutingContext; +class ScriptValue; // Exchange data struct between dart and C++ struct NativeValue { - double float64; union { int64_t int64; void* ptr; @@ -35,16 +47,22 @@ struct NativeValue { struct NativeFunctionContext; -using CallNativeFunction = void (*)(NativeFunctionContext* functionContext, int32_t argc, NativeValue* argv, NativeValue* returnValue); +using CallNativeFunction = void (*)(NativeFunctionContext* functionContext, + int32_t argc, + NativeValue* argv, + NativeValue* returnValue); -static void call_native_function(NativeFunctionContext* functionContext, int32_t argc, NativeValue* argv, NativeValue* returnValue); +static void call_native_function(NativeFunctionContext* functionContext, + int32_t argc, + NativeValue* argv, + NativeValue* returnValue); struct NativeFunctionContext { CallNativeFunction call; - NativeFunctionContext(ExecutionContext* context, JSValue callback); + NativeFunctionContext(ExecutingContext* context, JSValue callback); ~NativeFunctionContext(); JSValue m_callback{JS_NULL}; - ExecutionContext* m_context{nullptr}; + ExecutingContext* m_context{nullptr}; JSContext* m_ctx{nullptr}; list_head link; }; @@ -54,14 +72,10 @@ NativeValue Native_NewString(NativeString* string); NativeValue Native_NewCString(std::string string); NativeValue Native_NewFloat64(double value); NativeValue Native_NewBool(bool value); -NativeValue Native_NewInt32(int32_t value); +NativeValue Native_NewInt64(int64_t value); NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr); -NativeValue Native_NewJSON(ExecutionContext* context, JSValue& value); -NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value); -JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value); - -std::string nativeStringToStdString(NativeString* nativeString); +NativeValue Native_NewJSON(const ScriptValue& value); -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_NATIVE_VALUE_H diff --git a/bridge/foundation/native_value_converter.cc b/bridge/foundation/native_value_converter.cc new file mode 100644 index 0000000000..2825bab773 --- /dev/null +++ b/bridge/foundation/native_value_converter.cc @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "native_value_converter.h" + +namespace kraken { + +#define AnonymousFunctionCallPreFix "_anonymous_fn_" +#define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" + +// void call_native_function(NativeFunctionContext* functionContext, +// int32_t argc, +// NativeValue* argv, +// NativeValue* returnValue) { +// // auto* context = functionContext->context_; +// // auto* arguments = new JSValue[argc]; +// // for (int i = 0; i < argc; i++) { +// // arguments[i] = nativeValueToJSValue(context, argv[i]); +// // } +// // JSValue result = JS_Call(context->ctx(), functionContext->m_callback, context->Global(), argc, arguments); +// // context->DrainPendingPromiseJobs(); +// // if (context->HandleException(&result)) { +// // *returnValue = jsValueToNativeValue(context->ctx(), result); +// // } +// // +// // JS_FreeValue(context->ctx(), result); +// // +// // for (int i = 0; i < argc; i++) { +// // JS_FreeValue(context->ctx(), arguments[i]); +// // } +// // delete[] arguments; +// // delete functionContext; +//} + +static JSValue anonymousFunction(JSContext* ctx, + JSValueConst this_val, + int argc, + JSValueConst* argv, + int magic, + JSValue* func_data) { + auto id = magic; + // auto* eventTarget = static_cast<EventTarget*>(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + // + // std::string call_params = AnonymousFunctionCallPreFix + std::to_string(id); + // + // auto* arguments = new NativeValue[argc]; + // for (int i = 0; i < argc; i++) { + // arguments[i] = jsValueToNativeValue(ctx, argv[i]); + // } + // + // JSValue returnValue = eventTarget->callNativeMethods(call_params.c_str(), argc, arguments); + // delete[] arguments; + // return returnValue; +} + +void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int32_t contextId, const char* errmsg) { + // auto* promiseContext = static_cast<PromiseContext*>(callbackContext); + // if (!promiseContext->context->IsValid()) + // return; + // if (promiseContext->context->contextId() != contextId) + // return; + // + // auto* context = promiseContext->context; + // + // if (nativeValue != nullptr) { + // JSValue value = nativeValueToJSValue(promiseContext->context, *nativeValue); + // JSValue returnValue = JS_Call(context->ctx(), promiseContext->resolveFunc, context->Global(), 1, &value); + // context->DrainPendingPromiseJobs(); + // context->HandleException(&returnValue); + // JS_FreeValue(context->ctx(), value); + // JS_FreeValue(context->ctx(), returnValue); + // } else if (errmsg != nullptr) { + // JSValue error = JS_NewError(context->ctx()); + // JS_DefinePropertyValueStr(context->ctx(), error, "message", JS_NewString(context->ctx(), errmsg), + // JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JSValue returnValue = JS_Call(context->ctx(), + // promiseContext->rejectFunc, context->Global(), 1, &error); context->DrainPendingPromiseJobs(); + // context->HandleException(&returnValue); + // JS_FreeValue(context->ctx(), error); + // JS_FreeValue(context->ctx(), returnValue); + // } + // + // JS_FreeValue(context->ctx(), promiseContext->resolveFunc); + // JS_FreeValue(context->ctx(), promiseContext->rejectFunc); + // list_del(&promiseContext->link); +} + +static JSValue anonymousAsyncFunction(JSContext* ctx, + JSValueConst this_val, + int argc, + JSValueConst* argv, + int magic, + JSValue* func_data) { + // JSValue resolving_funcs[2]; + // JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); + // + // auto id = magic; + // auto* eventTarget = static_cast<EventTargetInstance*>(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + // auto* context = eventTarget->context(); + // + // auto* promiseContext = new PromiseContext{eventTarget, context, resolving_funcs[0], resolving_funcs[1], promise}; + // list_add_tail(&promiseContext->link, &context->promise_job_list); + // + // std::string call_params = AsyncAnonymousFunctionCallPreFix + std::to_string(id); + // + // auto* arguments = new NativeValue[argc + 3]; + // + // arguments[0] = Native_NewInt32(context->getContextId()); + // arguments[1] = Native_NewPtr(JSPointerType::AsyncContextContext, promiseContext); + // arguments[2] = Native_NewPtr(JSPointerType::AsyncContextContext, reinterpret_cast<void*>(anonymousAsyncCallback)); + // for (int i = 0; i < argc; i++) { + // arguments[i + 3] = jsValueToNativeValue(ctx, argv[i]); + // } + // + // eventTarget->callNativeMethods(call_params.c_str(), argc + 3, arguments); + // delete[] arguments; + // + // return promise; + return JS_NULL; +} + +std::shared_ptr<QJSFunction> CreateSyncCallback(JSContext* ctx, int function_id) { + JSValue callback = JS_NewCFunctionData(ctx, anonymousFunction, 4, function_id, 0, nullptr); + auto result = QJSFunction::Create(ctx, callback); + JS_FreeValue(ctx, callback); + return result; +} + +std::shared_ptr<QJSFunction> CreateAsyncCallback(JSContext* ctx, int function_id) { + JSValue callback = JS_NewCFunctionData(ctx, anonymousAsyncFunction, 4, function_id, 0, nullptr); + auto result = QJSFunction::Create(ctx, callback); + JS_FreeValue(ctx, callback); + return result; +} + +} // namespace kraken diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h new file mode 100644 index 0000000000..77691dd6c2 --- /dev/null +++ b/bridge/foundation/native_value_converter.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ +#define KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ + +#include "core/dom/binding_object.h" +#include "native_type.h" +#include "native_value.h" + +namespace kraken { + +// NativeValueConverter converts types back and forth from C++ types to NativeValue. The template +// parameter |T| determines what kind of type conversion to perform. +template <typename T, typename SFINAEHelper = void> +struct NativeValueConverter { + using ImplType = T; +}; + +template <typename T> +struct NativeValueConverterBase { + using ImplType = typename T::ImplType; +}; + +template <> +struct NativeValueConverter<NativeTypeNull> : public NativeValueConverterBase<NativeTypeNull> { + static NativeValue ToNativeValue() { return Native_NewNull(); } + + static ImplType FromNativeValue(JSContext* ctx) { return ScriptValue::Empty(ctx); } +}; + +template <> +struct NativeValueConverter<NativeTypeString> : public NativeValueConverterBase<NativeTypeString> { + static NativeValue ToNativeValue(ImplType value) { return Native_NewString(value); } + + static ImplType FromNativeValue(NativeValue value) { return static_cast<NativeString*>(value.u.ptr); } +}; + +template <> +struct NativeValueConverter<NativeTypeBool> : public NativeValueConverterBase<NativeTypeBool> { + static NativeValue ToNativeValue(ImplType value) { return Native_NewBool(value); } + + static ImplType FromNativeValue(NativeValue value) { return value.u.int64 == 1; } +}; + +template <> +struct NativeValueConverter<NativeTypeInt64> : public NativeValueConverterBase<NativeTypeInt64> { + static NativeValue ToNativeValue(ImplType value) { return Native_NewInt64(value); } + + static ImplType FromNativeValue(NativeValue value) { return value.u.int64; } +}; + +template <> +struct NativeValueConverter<NativeTypeDouble> : public NativeValueConverterBase<NativeTypeDouble> { + static NativeValue ToNativeValue(ImplType value) { return Native_NewFloat64(value); } + + static ImplType FromNativeValue(NativeValue value) { + double result; + memcpy(&result, reinterpret_cast<void*>(&value.u.int64), sizeof(double)); + return result; + } +}; + +template <> +struct NativeValueConverter<NativeTypeJSON> : public NativeValueConverterBase<NativeTypeJSON> { + static NativeValue ToNativeValue(ImplType value) { return Native_NewJSON(value); } + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { + auto* str = static_cast<const char*>(value.u.ptr); + return ScriptValue::CreateJsonObject(ctx, str, strlen(str)); + } +}; + +class NativeBoundingClientRect; +class BindingObject; +struct NativeBindingObject; +class NativeScreen; +class NativeCanvasRenderingContext2D; + +template <> +struct NativeValueConverter<NativeTypePointer<NativeBoundingClientRect>> + : public NativeValueConverterBase<NativeTypePointer<NativeBoundingClientRect>> { + static NativeValue ToNativeValue(ImplType value) { + return Native_NewPtr(JSPointerType::NativeBoundingClientRect, value); + } + static ImplType FromNativeValue(NativeValue value) { return static_cast<ImplType>(value.u.ptr); } +}; + +template <> +struct NativeValueConverter<NativeTypePointer<NativeBindingObject>> + : public NativeValueConverterBase<NativeTypePointer<NativeBindingObject>> { + static NativeValue ToNativeValue(ImplType value) { return Native_NewPtr(JSPointerType::BindingObject, value); } + static ImplType FromNativeValue(NativeValue value) { return static_cast<ImplType>(value.u.ptr); } +}; + +template <> +struct NativeValueConverter<NativeTypePointer<NativeCanvasRenderingContext2D>> + : public NativeValueConverterBase<NativeTypePointer<NativeCanvasRenderingContext2D>> { + static NativeValue ToNativeValue(ImplType value) { + return Native_NewPtr(JSPointerType::NativeCanvasRenderingContext2D, value); + } + static ImplType FromNativeValue(NativeValue value) { return static_cast<ImplType>(value.u.ptr); } +}; + +std::shared_ptr<QJSFunction> CreateSyncCallback(JSContext* ctx, int function_id); +std::shared_ptr<QJSFunction> CreateAsyncCallback(JSContext* ctx, int function_id); + +template <> +struct NativeValueConverter<NativeTypeFunction> : public NativeValueConverterBase<NativeTypeFunction> { + static NativeValue ToNativeValue(ImplType value) { + // Not supported. + assert(false); + } + + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { return CreateSyncCallback(ctx, value.u.int64); }; +}; + +template <> +struct NativeValueConverter<NativeTypeAsyncFunction> : public NativeValueConverterBase<NativeTypeAsyncFunction> { + static NativeValue ToNativeValue(ImplType value) { + // Not supported. + assert(false); + } + + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { return CreateAsyncCallback(ctx, value.u.int64); } +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ diff --git a/bridge/foundation/ref_counted_internal.h b/bridge/foundation/ref_counted_internal.h index 25c11ac36e..148ebf623d 100644 --- a/bridge/foundation/ref_counted_internal.h +++ b/bridge/foundation/ref_counted_internal.h @@ -11,7 +11,6 @@ #define FLUTTER_FML_MEMORY_REF_COUNTED_INTERNAL_H_ #include <atomic> -#include "include/kraken_bridge.h" #include "logging.h" namespace fml { @@ -79,8 +78,6 @@ class RefCountedThreadSafeBase { mutable bool adoption_required_; mutable bool destruction_started_; #endif - - KRAKEN_DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafeBase); }; inline RefCountedThreadSafeBase::RefCountedThreadSafeBase() diff --git a/bridge/foundation/ref_ptr.h b/bridge/foundation/ref_ptr.h index eab16fc2d7..0c4c420bc0 100644 --- a/bridge/foundation/ref_ptr.h +++ b/bridge/foundation/ref_ptr.h @@ -16,6 +16,7 @@ #include <utility> #include "logging.h" +#include "macros.h" #include "ref_ptr_internal.h" namespace fml { diff --git a/bridge/foundation/ref_ptr_internal.h b/bridge/foundation/ref_ptr_internal.h index 15e95e3ec3..45a04c0848 100644 --- a/bridge/foundation/ref_ptr_internal.h +++ b/bridge/foundation/ref_ptr_internal.h @@ -9,7 +9,6 @@ #define FLUTTER_FML_MEMORY_REF_PTR_INTERNAL_H_ #include <utility> -#include "include/kraken_bridge.h" namespace fml { diff --git a/bridge/foundation/string_view.cc b/bridge/foundation/string_view.cc new file mode 100644 index 0000000000..bd59577be3 --- /dev/null +++ b/bridge/foundation/string_view.cc @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "string_view.h" + +namespace kraken { + +StringView::StringView(const std::string& string) : bytes_(string.data()), length_(string.length()), is_8bit_(true) {} + +StringView::StringView(const NativeString* string) + : bytes_(string->string()), length_(string->length()), is_8bit_(false) {} + +StringView::StringView(void* bytes, unsigned length, bool is_wide_char) + : bytes_(bytes), length_(length), is_8bit_(!is_wide_char) {} +} // namespace kraken diff --git a/bridge/foundation/string_view.h b/bridge/foundation/string_view.h new file mode 100644 index 0000000000..a8b2871618 --- /dev/null +++ b/bridge/foundation/string_view.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_FOUNDATION_STRING_VIEW_H_ +#define KRAKENBRIDGE_FOUNDATION_STRING_VIEW_H_ + +#include <string> +#include "ascii_types.h" +#include "native_string.h" + +namespace kraken { + +class StringView final { + public: + StringView() = delete; + + explicit StringView(const std::string& string); + explicit StringView(const NativeString* string); + explicit StringView(void* bytes, unsigned length, bool is_wide_char); + + bool Is8Bit() const { return is_8bit_; } + + bool IsLowerASCII() const { + if (is_8bit_) { + return kraken::IsLowerASCII(Characters8(), length()); + } + return kraken::IsLowerASCII(Characters16(), length()); + } + + const char* Characters8() const { return static_cast<const char*>(bytes_); } + + const char16_t* Characters16() const { return static_cast<const char16_t*>(bytes_); } + + unsigned length() const { return length_; } + bool Empty() const { return length_ == 0; } + + private: + const void* bytes_; + unsigned length_; + unsigned is_8bit_ : 1; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_FOUNDATION_STRING_VIEW_H_ diff --git a/bridge/foundation/task_queue.cc b/bridge/foundation/task_queue.cc index 9dee3bc04f..34c69648cf 100644 --- a/bridge/foundation/task_queue.cc +++ b/bridge/foundation/task_queue.cc @@ -4,7 +4,7 @@ #include "task_queue.h" -namespace foundation { +namespace kraken { int32_t TaskQueue::registerTask(const Task& task, void* data) { std::lock_guard<std::mutex> guard(queue_mutex_); @@ -31,4 +31,4 @@ void TaskQueue::flushTask() { m_map.clear(); } -} // namespace foundation +} // namespace kraken diff --git a/bridge/foundation/task_queue.h b/bridge/foundation/task_queue.h index c99f19acf7..836084d750 100644 --- a/bridge/foundation/task_queue.h +++ b/bridge/foundation/task_queue.h @@ -7,11 +7,12 @@ #include <mutex> #include <unordered_map> -#include "closure.h" #include "ref_counter.h" #include "ref_ptr.h" -namespace foundation { +namespace kraken { + +using Task = void (*)(void*); class TaskQueue : public fml::RefCountedThreadSafe<TaskQueue> { public: @@ -34,6 +35,6 @@ class TaskQueue : public fml::RefCountedThreadSafe<TaskQueue> { FML_FRIEND_REF_COUNTED_THREAD_SAFE(TaskQueue); }; -} // namespace foundation +} // namespace kraken #endif // KRAKENBRIDGE_TASK_QUEUE_H diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 64fd8f784c..bb02a261e7 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -3,55 +3,35 @@ */ #include "ui_command_buffer.h" -#include "dart_methods.h" -#include "include/kraken_bridge.h" +#include "core/dart_methods.h" +#include "core/executing_context.h" +#include "foundation/logging.h" -namespace foundation { +namespace kraken { -UICommandBuffer::UICommandBuffer(int32_t contextId) : contextId(contextId) {} - -void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr, bool batchedUpdate) { - if (batchedUpdate) { - kraken::getDartMethod()->requestBatchUpdate(contextId); - update_batched = true; - } - - UICommandItem item{id, type, nativePtr}; - queue.emplace_back(item); +UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context) { + queue.reserve(0); } -void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr) { - if (!update_batched) { -#if FLUTTER_BACKEND - kraken::getDartMethod()->requestBatchUpdate(contextId); -#endif - update_batched = true; - } - - UICommandItem item{id, type, nativePtr}; +void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr) { + UICommandItem item{id, static_cast<int32_t>(type), nativePtr}; queue.emplace_back(item); } -void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01, void* nativePtr) { - if (!update_batched) { -#if FLUTTER_BACKEND - kraken::getDartMethod()->requestBatchUpdate(contextId); - update_batched = true; -#endif - } - - UICommandItem item{id, type, args_01, nativePtr}; +void UICommandBuffer::addCommand(int32_t id, UICommand type, std::unique_ptr<NativeString>&& args_01, void* nativePtr) { + assert(args_01 != nullptr); + UICommandItem item{id, static_cast<int32_t>(type), args_01.release(), nativePtr}; queue.emplace_back(item); } -void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01, NativeString& args_02, void* nativePtr) { -#if FLUTTER_BACKEND - if (!update_batched) { - kraken::getDartMethod()->requestBatchUpdate(contextId); - update_batched = true; - } -#endif - UICommandItem item{id, type, args_01, args_02, nativePtr}; +void UICommandBuffer::addCommand(int32_t id, + UICommand type, + std::unique_ptr<NativeString>&& args_01, + std::unique_ptr<NativeString>&& args_02, + void* nativePtr) { + assert(args_01 != nullptr); + assert(args_02 != nullptr); + UICommandItem item{id, static_cast<int32_t>(type), args_01.release(), args_02.release(), nativePtr}; queue.emplace_back(item); } @@ -69,7 +49,6 @@ void UICommandBuffer::clear() { delete[] reinterpret_cast<const uint16_t*>(command.string_02); } queue.clear(); - update_batched = false; } -} // namespace foundation +} // namespace kraken diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index bf25bac083..28386daf82 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -5,28 +5,79 @@ #ifndef KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ #define KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ -#include "include/kraken_bridge.h" +#include <cinttypes> +#include <vector> +#include "bindings/qjs/native_string_utils.h" +#include "native_value.h" -namespace foundation { +namespace kraken { + +class ExecutingContext; + +enum class UICommand { + kCreateElement, + kCreateTextNode, + kCreateComment, + kCreateDocument, + kCreateWindow, + kDisposeEventTarget, + kAddEvent, + kRemoveNode, + kInsertAdjacentNode, + kSetStyle, + kSetAttribute, + kRemoveAttribute, + kCloneNode, + kRemoveEvent, + kCreateDocumentFragment, +}; + +struct UICommandItem { + UICommandItem(int32_t id, int32_t type, NativeString* args_01, NativeString* args_02, void* nativePtr) + : type(type), + string_01(reinterpret_cast<int64_t>((new NativeString(args_01))->string())), + args_01_length(args_01->length()), + string_02(reinterpret_cast<int64_t>((new NativeString(args_02))->string())), + args_02_length(args_02->length()), + id(id), + nativePtr(reinterpret_cast<int64_t>(nativePtr)){}; + UICommandItem(int32_t id, int32_t type, NativeString* args_01, void* nativePtr) + : type(type), + string_01(reinterpret_cast<int64_t>((new NativeString(args_01))->string())), + args_01_length(args_01->length()), + id(id), + nativePtr(reinterpret_cast<int64_t>(nativePtr)){}; + UICommandItem(int32_t id, int32_t type, void* nativePtr) + : type(type), id(id), nativePtr(reinterpret_cast<int64_t>(nativePtr)){}; + int32_t type; + int32_t id; + int32_t args_01_length{0}; + int32_t args_02_length{0}; + int64_t string_01{0}; + int64_t string_02{0}; + int64_t nativePtr{0}; +}; class UICommandBuffer { public: UICommandBuffer() = delete; - explicit UICommandBuffer(int32_t contextId); - void addCommand(int32_t id, int32_t type, void* nativePtr, bool batchedUpdate); - void addCommand(int32_t id, int32_t type, void* nativePtr); - void addCommand(int32_t id, int32_t type, NativeString& args_01, NativeString& args_02, void* nativePtr); - void addCommand(int32_t id, int32_t type, NativeString& args_01, void* nativePtr); + explicit UICommandBuffer(ExecutingContext* context); + void addCommand(int32_t id, UICommand type, void* nativePtr); + void addCommand(int32_t id, + UICommand type, + std::unique_ptr<NativeString>&& args_01, + std::unique_ptr<NativeString>&& args_02, + void* nativePtr); + void addCommand(int32_t id, UICommand type, std::unique_ptr<NativeString>&& args_01, void* nativePtr); UICommandItem* data(); int64_t size(); void clear(); private: - int32_t contextId; - std::atomic<bool> update_batched{false}; + ExecutingContext* context_{nullptr}; std::vector<UICommandItem> queue; }; -} // namespace foundation +} // namespace kraken #endif // KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ diff --git a/bridge/foundation/ui_command_callback_queue.cc b/bridge/foundation/ui_command_callback_queue.cc deleted file mode 100644 index df3d0e0e0e..0000000000 --- a/bridge/foundation/ui_command_callback_queue.cc +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2020-present The Kraken authors. All rights reserved. - */ - -#include "kraken_bridge.h" - -namespace foundation { - -UICommandCallbackQueue* UICommandCallbackQueue::instance() { - static UICommandCallbackQueue* queue = nullptr; - - if (queue == nullptr) { - queue = new UICommandCallbackQueue(); - } - - return queue; -} - -void UICommandCallbackQueue::flushCallbacks() { - for (auto& item : queue) { - item.callback(item.data); - } - queue.clear(); -} - -void UICommandCallbackQueue::registerCallback(const Callback& callback, void* data) { - CallbackItem item{callback, data}; - queue.emplace_back(item); -} - -} // namespace foundation diff --git a/bridge/foundation/ui_task_queue.cc b/bridge/foundation/ui_task_queue.cc index ae90b011a1..d9016756b3 100644 --- a/bridge/foundation/ui_task_queue.cc +++ b/bridge/foundation/ui_task_queue.cc @@ -4,14 +4,13 @@ #include "ui_task_queue.h" -namespace foundation { +namespace kraken { std::mutex UITaskQueue::ui_task_creation_mutex_{}; fml::RefPtr<UITaskQueue> UITaskQueue::instance_{}; int32_t UITaskQueue::registerTask(const Task& task, void* data) { int32_t taskId = TaskQueue::registerTask(task, data); - assert(std::this_thread::get_id() != getUIThreadId()); return taskId; } -} // namespace foundation +} // namespace kraken diff --git a/bridge/foundation/ui_task_queue.h b/bridge/foundation/ui_task_queue.h index fc730c1431..26c7a347b3 100644 --- a/bridge/foundation/ui_task_queue.h +++ b/bridge/foundation/ui_task_queue.h @@ -7,9 +7,7 @@ #include "task_queue.h" -namespace foundation { - -using Task = void (*)(void*); +namespace kraken { class UITaskQueue : public TaskQueue { public: @@ -29,6 +27,6 @@ class UITaskQueue : public TaskQueue { int m_contextId; }; -} // namespace foundation +} // namespace kraken #endif // KRAKENBRIDGE_UI_TASK_QUEUE_H diff --git a/bridge/include/kraken_bridge.h b/bridge/include/kraken_bridge.h index 6fb052d485..28fc928636 100644 --- a/bridge/include/kraken_bridge.h +++ b/bridge/include/kraken_bridge.h @@ -5,35 +5,17 @@ #ifndef KRAKEN_BRIDGE_EXPORT_H #define KRAKEN_BRIDGE_EXPORT_H -#include <cstdint> #include <thread> -#include "dart_methods.h" -#include "kraken_foundation.h" - -#if KRAKEN_JSC_ENGINE -#include "kraken_bridge_jsc.h" -#elif KRAKEN_QUICK_JS_ENGINE -#endif - #define KRAKEN_EXPORT_C extern "C" __attribute__((visibility("default"))) __attribute__((used)) #define KRAKEN_EXPORT __attribute__((__visibility__("default"))) KRAKEN_EXPORT std::thread::id getUIThreadId(); -struct NativeString { - const uint16_t* string; - uint32_t length; - - NativeString* clone(); - void free(); -}; - -struct NativeByteCode { - uint8_t* bytes; - int32_t length; -}; +typedef struct NativeString NativeString; +typedef struct NativeScreen NativeScreen; +typedef struct NativeByteCode NativeByteCode; struct KrakenInfo; @@ -44,48 +26,6 @@ struct KrakenInfo { const char* system_name{nullptr}; }; -struct NativeScreen { - double width; - double height; -}; - -enum UICommand { - createElement, - createTextNode, - createComment, - disposeEventTarget, - addEvent, - removeNode, - insertAdjacentNode, - setStyle, - setAttribute, - removeAttribute, - cloneNode, - removeEvent, - createDocumentFragment, -}; - -struct KRAKEN_EXPORT UICommandItem { - UICommandItem(int32_t id, int32_t type, NativeString args_01, NativeString args_02, void* nativePtr) - : type(type), - string_01(reinterpret_cast<int64_t>(args_01.string)), - args_01_length(args_01.length), - string_02(reinterpret_cast<int64_t>(args_02.string)), - args_02_length(args_02.length), - id(id), - nativePtr(reinterpret_cast<int64_t>(nativePtr)){}; - UICommandItem(int32_t id, int32_t type, NativeString args_01, void* nativePtr) - : type(type), string_01(reinterpret_cast<int64_t>(args_01.string)), args_01_length(args_01.length), id(id), nativePtr(reinterpret_cast<int64_t>(nativePtr)){}; - UICommandItem(int32_t id, int32_t type, void* nativePtr) : type(type), id(id), nativePtr(reinterpret_cast<int64_t>(nativePtr)){}; - int32_t type; - int32_t id; - int32_t args_01_length{0}; - int32_t args_02_length{0}; - int64_t string_01{0}; - int64_t string_02{0}; - int64_t nativePtr{0}; -}; - typedef void (*Task)(void*); typedef void (*ConsoleMessageHandler)(void* ctx, const std::string& message, int logLevel); @@ -100,7 +40,7 @@ void* getPage(int32_t contextId); bool checkPage(int32_t contextId); bool checkPage(int32_t contextId, void* context); KRAKEN_EXPORT_C -void evaluateScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine); +void evaluateScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int32_t startLine); KRAKEN_EXPORT_C void evaluateQuickjsByteCode(int32_t contextId, uint8_t* bytes, int32_t byteLen); KRAKEN_EXPORT_C @@ -108,11 +48,13 @@ void parseHTML(int32_t contextId, const char* code, int32_t length); KRAKEN_EXPORT_C void reloadJsContext(int32_t contextId); KRAKEN_EXPORT_C -void invokeModuleEvent(int32_t contextId, NativeString* module, const char* eventType, void* event, NativeString* extra); -KRAKEN_EXPORT_C -void registerDartMethods(uint64_t* methodBytes, int32_t length); +void invokeModuleEvent(int32_t contextId, + NativeString* module, + const char* eventType, + void* event, + NativeString* extra); KRAKEN_EXPORT_C -NativeScreen* createScreen(double width, double height); +void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length); KRAKEN_EXPORT_C KrakenInfo* getKrakenInfo(); KRAKEN_EXPORT_C @@ -122,9 +64,7 @@ void flushUITask(int32_t contextId); KRAKEN_EXPORT_C void registerUITask(int32_t contextId, Task task, void* data); KRAKEN_EXPORT_C -void flushUICommandCallback(); -KRAKEN_EXPORT_C -UICommandItem* getUICommandItems(int32_t contextId); +void* getUICommandItems(int32_t contextId); KRAKEN_EXPORT_C int64_t getUICommandItemSize(int32_t contextId); KRAKEN_EXPORT_C diff --git a/bridge/include/kraken_bridge_test.h b/bridge/include/kraken_bridge_test.h index 97a2c44c99..68ea49be02 100644 --- a/bridge/include/kraken_bridge_test.h +++ b/bridge/include/kraken_bridge_test.h @@ -5,20 +5,19 @@ #ifndef KRAKEN_BRIDGE_TEST_EXPORT_H #define KRAKEN_BRIDGE_TEST_EXPORT_H -#include <cstdint> #include "kraken_bridge.h" KRAKEN_EXPORT_C void initTestFramework(int32_t contextId); KRAKEN_EXPORT_C -int8_t evaluateTestScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine); +int8_t evaluateTestScripts(int32_t contextId, void* code, const char* bundleFilename, int startLine); -using ExecuteCallback = void* (*)(int32_t contextId, NativeString* status); +using ExecuteCallback = void* (*)(int32_t contextId, void* status); KRAKEN_EXPORT_C void executeTest(int32_t contextId, ExecuteCallback executeCallback); KRAKEN_EXPORT_C -void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length); +void registerTestEnvDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length); #endif diff --git a/bridge/include/kraken_foundation.h b/bridge/include/kraken_foundation.h deleted file mode 100644 index 30e27dedb1..0000000000 --- a/bridge/include/kraken_foundation.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2020 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_FOUNDATION_H -#define KRAKENBRIDGE_FOUNDATION_H - -#include <atomic> -#include <cassert> -#include <codecvt> -#include <cstdint> -#include <functional> -#include <locale> -#include <sstream> -#include <string> -#include <unordered_map> -#include <vector> -#define WINDOW_TARGET_ID -1 -#define DOCUMENT_TARGET_ID -2 - -#define assert_m(exp, msg) assert(((void)msg, exp)) - -#define KRAKEN_EXPORT __attribute__((__visibility__("default"))) - -#if defined(__GNUC__) || defined(__clang__) -#define LIKELY(x) __builtin_expect(!!(x), 1) -#define UNLIKELY(x) __builtin_expect(!!(x), 0) -#define FORCE_INLINE inline __attribute__((always_inline)) -#else -#define LIKELY(x) (x) -#define UNLIKELY(x) (x) -#define FORCE_INLINE inline -#endif - -#define KRAKEN_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete - -#define KRAKEN_DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete - -#define KRAKEN_DISALLOW_MOVE(TypeName) \ - TypeName(TypeName&&) = delete; \ - TypeName& operator=(TypeName&&) = delete - -#define KRAKEN_DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&) = delete; \ - TypeName& operator=(const TypeName&) = delete - -#define KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) \ - TypeName(const TypeName&) = delete; \ - TypeName(TypeName&&) = delete; \ - TypeName& operator=(const TypeName&) = delete; \ - TypeName& operator=(TypeName&&) = delete - -#define KRAKEN_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ - TypeName() = delete; \ - KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) - -struct NativeString; -struct UICommandItem; - -namespace foundation { - -// An un thread safe queue used for dart side to read ui command items. -class UICommandCallbackQueue { - public: - using Callback = void (*)(void*); - UICommandCallbackQueue() = default; - static KRAKEN_EXPORT UICommandCallbackQueue* instance(); - KRAKEN_EXPORT void registerCallback(const Callback& callback, void* data); - KRAKEN_EXPORT void flushCallbacks(); - - private: - struct CallbackItem { - CallbackItem(const Callback& callback, void* data) : callback(callback), data(data){}; - Callback callback; - void* data; - }; - - std::vector<CallbackItem> queue; -}; - -} // namespace foundation - -template <typename T> -std::string toUTF8(const std::basic_string<T, std::char_traits<T>, std::allocator<T>>& source) { - std::string result; - - std::wstring_convert<std::codecvt_utf8_utf16<T>, T> convertor; - result = convertor.to_bytes(source); - - return result; -} - -template <typename T> -void fromUTF8(const std::string& source, std::basic_string<T, std::char_traits<T>, std::allocator<T>>& result) { - std::wstring_convert<std::codecvt_utf8_utf16<T>, T> convertor; - result = convertor.from_bytes(source); -} - -#endif diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 872b5f8587..0de3d93808 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -2,24 +2,17 @@ * Copyright (C) 2019-present The Kraken authors. All rights reserved. */ -#include "kraken_bridge.h" +#include <atomic> #include <cassert> -#include "dart_methods.h" +#include <thread> + +#include "bindings/qjs/native_string_utils.h" +#include "core/page.h" #include "foundation/inspector_task_queue.h" #include "foundation/logging.h" +#include "foundation/ui_command_buffer.h" #include "foundation/ui_task_queue.h" -#if KRAKEN_JSC_ENGINE -#include "bindings/jsc/KOM/performance.h" -#elif KRAKEN_QUICK_JS_ENGINE -#include "page.h" -#endif - -#if KRAKEN_JSC_ENGINE -#include "bridge_jsc.h" -#endif - -#include <atomic> -#include <thread> +#include "include/kraken_bridge.h" #if defined(_WIN32) #define SYSTEM_NAME "windows" // Windows @@ -48,24 +41,6 @@ std::atomic<bool> inited{false}; std::atomic<int32_t> poolIndex{0}; int maxPoolSize = 0; -NativeScreen screen; - -std::thread::id uiThreadId; - -std::thread::id getUIThreadId() { - return uiThreadId; -} - -void printError(int32_t contextId, const char* errmsg) { - if (kraken::getDartMethod()->onJsError != nullptr) { - kraken::getDartMethod()->onJsError(contextId, errmsg); - } - if (kraken::getDartMethod()->onJsLog != nullptr) { - kraken::getDartMethod()->onJsLog(contextId, static_cast<int>(foundation::MessageLevel::Error), errmsg); - } - - KRAKEN_LOG(ERROR) << errmsg << std::endl; -} namespace { @@ -89,7 +64,6 @@ int32_t searchForAvailableContextId() { } // namespace void initJSPagePool(int poolSize) { - uiThreadId = std::this_thread::get_id(); // When dart hot restarted, should dispose previous bridge and clear task message queue. if (inited) { disposeAllPages(); @@ -99,7 +73,7 @@ void initJSPagePool(int poolSize) { kraken::KrakenPage::pageContextPool[i] = nullptr; } - kraken::KrakenPage::pageContextPool[0] = new kraken::KrakenPage(0, printError); + kraken::KrakenPage::pageContextPool[0] = new kraken::KrakenPage(0, nullptr); inited = true; maxPoolSize = poolSize; } @@ -124,8 +98,10 @@ int32_t allocateNewPage(int32_t targetContextId) { } assert(kraken::KrakenPage::pageContextPool[targetContextId] == nullptr && - (std::string("can not allocate page at index") + std::to_string(targetContextId) + std::string(": page have already exist.")).c_str()); - auto* page = new kraken::KrakenPage(targetContextId, printError); + (std::string("can not Allocate page at index") + std::to_string(targetContextId) + + std::string(": page have already exist.")) + .c_str()); + auto* page = new kraken::KrakenPage(targetContextId, nullptr); kraken::KrakenPage::pageContextPool[targetContextId] = page; return targetContextId; } @@ -144,13 +120,13 @@ bool checkPage(int32_t contextId, void* context) { if (kraken::KrakenPage::pageContextPool[contextId] == nullptr) return false; auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); - return page->getContext() == context; + return page->GetExecutingContext() == context; } void evaluateScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine) { assert(checkPage(contextId) && "evaluateScripts: contextId is not valid"); auto context = static_cast<kraken::KrakenPage*>(getPage(contextId)); - context->evaluateScript(code, bundleFilename, startLine); + context->evaluateScript(reinterpret_cast<kraken::NativeString*>(code), bundleFilename, startLine); } void evaluateQuickjsByteCode(int32_t contextId, uint8_t* bytes, int32_t byteLen) { @@ -169,25 +145,26 @@ void reloadJsContext(int32_t contextId) { assert(checkPage(contextId) && "reloadJSContext: contextId is not valid"); auto bridgePtr = getPage(contextId); auto context = static_cast<kraken::KrakenPage*>(bridgePtr); - auto newContext = new kraken::KrakenPage(contextId, printError); + auto newContext = new kraken::KrakenPage(contextId, nullptr); delete context; kraken::KrakenPage::pageContextPool[contextId] = newContext; } -void invokeModuleEvent(int32_t contextId, NativeString* moduleName, const char* eventType, void* event, NativeString* extra) { +void invokeModuleEvent(int32_t contextId, + NativeString* moduleName, + const char* eventType, + void* event, + NativeString* extra) { assert(checkPage(contextId) && "invokeEventListener: contextId is not valid"); auto context = static_cast<kraken::KrakenPage*>(getPage(contextId)); - context->invokeModuleEvent(moduleName, eventType, event, extra); + context->invokeModuleEvent(reinterpret_cast<kraken::NativeString*>(moduleName), eventType, event, + reinterpret_cast<kraken::NativeString*>(extra)); } -void registerDartMethods(uint64_t* methodBytes, int32_t length) { - kraken::registerDartMethods(methodBytes, length); -} - -NativeScreen* createScreen(double width, double height) { - screen.width = width; - screen.height = height; - return &screen; +void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length) { + assert(checkPage(contextId) && "registerDartMethods: contextId is not valid"); + auto context = static_cast<kraken::KrakenPage*>(getPage(contextId)); + context->registerDartMethods(methodBytes, length); } static KrakenInfo* krakenInfo{nullptr}; @@ -209,41 +186,38 @@ void setConsoleMessageHandler(ConsoleMessageHandler handler) { } void dispatchUITask(int32_t contextId, void* context, void* callback) { - assert(std::this_thread::get_id() == getUIThreadId()); + auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); + assert(std::this_thread::get_id() == page->currentThread()); reinterpret_cast<void (*)(void*)>(callback)(context); } void flushUITask(int32_t contextId) { - foundation::UITaskQueue::instance(contextId)->flushTask(); + kraken::UITaskQueue::instance(contextId)->flushTask(); } void registerUITask(int32_t contextId, Task task, void* data) { - foundation::UITaskQueue::instance(contextId)->registerTask(task, data); + kraken::UITaskQueue::instance(contextId)->registerTask(task, data); }; -void flushUICommandCallback() { - foundation::UICommandCallbackQueue::instance()->flushCallbacks(); -} - -UICommandItem* getUICommandItems(int32_t contextId) { +void* getUICommandItems(int32_t contextId) { auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); if (page == nullptr) return nullptr; - return page->getContext()->uiCommandBuffer()->data(); + return page->GetExecutingContext()->uiCommandBuffer()->data(); } int64_t getUICommandItemSize(int32_t contextId) { auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); if (page == nullptr) return 0; - return page->getContext()->uiCommandBuffer()->size(); + return page->GetExecutingContext()->uiCommandBuffer()->size(); } void clearUICommandItems(int32_t contextId) { auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); if (page == nullptr) return; - page->getContext()->uiCommandBuffer()->clear(); + page->GetExecutingContext()->uiCommandBuffer()->clear(); } void registerContextDisposedCallbacks(int32_t contextId, Task task, void* data) { @@ -252,7 +226,7 @@ void registerContextDisposedCallbacks(int32_t contextId, Task task, void* data) } void registerPluginByteCode(uint8_t* bytes, int32_t length, const char* pluginName) { - kraken::KrakenPage::pluginByteCode[pluginName] = NativeByteCode{bytes, length}; + kraken::ExecutingContext::pluginByteCode[pluginName] = kraken::NativeByteCode{bytes, length}; } int32_t profileModeEnabled() { @@ -262,17 +236,3 @@ int32_t profileModeEnabled() { return 0; #endif } - -NativeString* NativeString::clone() { - auto* newNativeString = new NativeString(); - auto* newString = new uint16_t[length]; - - memcpy(newString, string, length * sizeof(uint16_t)); - newNativeString->string = newString; - newNativeString->length = length; - return newNativeString; -} - -void NativeString::free() { - delete[] string; -} diff --git a/bridge/kraken_bridge_test.cc b/bridge/kraken_bridge_test.cc index 9567b1c29b..2e9f0e61f0 100644 --- a/bridge/kraken_bridge_test.cc +++ b/bridge/kraken_bridge_test.cc @@ -3,33 +3,32 @@ */ #include "kraken_bridge_test.h" -#include "dart_methods.h" - -#if KRAKEN_JSC_ENGINE -#include "bridge_test_jsc.h" -#elif KRAKEN_QUICK_JS_ENGINE -#include "page_test.h" -#endif #include <atomic> +#include "bindings/qjs/native_string_utils.h" +#include "kraken_test_context.h" -std::unordered_map<int, kraken::KrakenPageTest*> bridgeTestPool = std::unordered_map<int, kraken::KrakenPageTest*>(); +std::unordered_map<int, kraken::KrakenTestContext*> testContextPool = + std::unordered_map<int, kraken::KrakenTestContext*>(); void initTestFramework(int32_t contextId) { auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); - auto bridgeTest = new kraken::KrakenPageTest(page); - bridgeTestPool[contextId] = bridgeTest; + auto testContext = new kraken::KrakenTestContext(page->GetExecutingContext()); + testContextPool[contextId] = testContext; } -int8_t evaluateTestScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine) { - auto bridgeTest = bridgeTestPool[contextId]; - return bridgeTest->evaluateTestScripts(code->string, code->length, bundleFilename, startLine); +int8_t evaluateTestScripts(int32_t contextId, void* code, const char* bundleFilename, int startLine) { + auto testContext = testContextPool[contextId]; + return testContext->evaluateTestScripts(static_cast<kraken::NativeString*>(code)->string(), + static_cast<kraken::NativeString*>(code)->length(), bundleFilename, + startLine); } void executeTest(int32_t contextId, ExecuteCallback executeCallback) { - auto bridgeTest = bridgeTestPool[contextId]; - bridgeTest->invokeExecuteTest(executeCallback); + auto testContext = testContextPool[contextId]; + testContext->invokeExecuteTest(executeCallback); } -void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length) { - kraken::registerTestEnvDartMethods(methodBytes, length); +void registerTestEnvDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length) { + auto testContext = testContextPool[contextId]; + testContext->registerTestEnvDartMethods(methodBytes, length); } diff --git a/bridge/page.cc b/bridge/page.cc deleted file mode 100644 index 356b57fd28..0000000000 --- a/bridge/page.cc +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "polyfill.h" - -#include <atomic> -#include "bindings/qjs/qjs_patch.h" -#include "dart_methods.h" -#include "page.h" - -#include "bindings/qjs/bom/blob.h" -#include "bindings/qjs/bom/console.h" -#include "bindings/qjs/bom/performance.h" -#include "bindings/qjs/bom/screen.h" -#include "bindings/qjs/bom/timer.h" -#include "bindings/qjs/bom/window.h" -#include "bindings/qjs/dom/comment_node.h" -#include "bindings/qjs/dom/custom_event.h" -#include "bindings/qjs/dom/document.h" -#include "bindings/qjs/dom/document_fragment.h" -#include "bindings/qjs/dom/element.h" -#include "bindings/qjs/dom/elements/.gen/anchor_element.h" -#include "bindings/qjs/dom/elements/.gen/canvas_element.h" -#include "bindings/qjs/dom/elements/.gen/input_element.h" -#include "bindings/qjs/dom/elements/.gen/object_element.h" -#include "bindings/qjs/dom/elements/.gen/script_element.h" -#include "bindings/qjs/dom/elements/.gen/textarea_element.h" -#include "bindings/qjs/dom/elements/image_element.h" -#include "bindings/qjs/dom/elements/template_element.h" -#include "bindings/qjs/dom/event.h" -#include "bindings/qjs/dom/event_target.h" -#include "bindings/qjs/dom/events/.gen/close_event.h" -#include "bindings/qjs/dom/events/.gen/gesture_event.h" -#include "bindings/qjs/dom/events/.gen/input_event.h" -#include "bindings/qjs/dom/events/.gen/intersection_change.h" -#include "bindings/qjs/dom/events/.gen/media_error_event.h" -#include "bindings/qjs/dom/events/.gen/message_event.h" -#include "bindings/qjs/dom/events/.gen/mouse_event.h" -#include "bindings/qjs/dom/events/.gen/popstate_event.h" -#include "bindings/qjs/dom/events/touch_event.h" -#include "bindings/qjs/dom/style_declaration.h" -#include "bindings/qjs/dom/text_node.h" -#include "bindings/qjs/module_manager.h" - -namespace kraken { - -using namespace binding::qjs; - -std::unordered_map<std::string, NativeByteCode> KrakenPage::pluginByteCode{}; -ConsoleMessageHandler KrakenPage::consoleMessageHandler{nullptr}; - -kraken::KrakenPage** KrakenPage::pageContextPool{nullptr}; - -KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : contextId(contextId) { -#if ENABLE_PROFILE - auto jsContextStartTime = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); -#endif - m_context = new ExecutionContext(contextId, handler, this); - -#if ENABLE_PROFILE - auto nativePerformance = Performance::instance(m_context)->m_nativePerformance; - nativePerformance.mark(PERF_JS_CONTEXT_INIT_START, jsContextStartTime); - nativePerformance.mark(PERF_JS_CONTEXT_INIT_END); - nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_START); -#endif - - bindConsole(m_context); - bindTimer(m_context); - bindScreen(m_context); - bindModuleManager(m_context); - bindEventTarget(m_context); - bindBlob(m_context); - bindWindow(m_context); - bindEvent(m_context); - bindCustomEvent(m_context); - bindNode(m_context); - bindDocumentFragment(m_context); - bindTextNode(m_context); - bindCommentNode(m_context); - bindElement(m_context); - bindAnchorElement(m_context); - bindCanvasElement(m_context); - bindImageElement(m_context); - bindInputElement(m_context); - bindTextareaElement(m_context); - bindObjectElement(m_context); - bindScriptElement(m_context); - bindTemplateElement(m_context); - bindCSSStyleDeclaration(m_context); - bindCloseEvent(m_context); - bindGestureEvent(m_context); - bindInputEvent(m_context); - bindIntersectionChangeEvent(m_context); - bindMediaErrorEvent(m_context); - bindMouseEvent(m_context); - bindMessageEvent(m_context); - bindPopStateEvent(m_context); - bindTouchEvent(m_context); - bindDocument(m_context); - bindPerformance(m_context); - -#if ENABLE_PROFILE - nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); - nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); -#endif - - initKrakenPolyFill(this); - - for (auto& p : pluginByteCode) { - evaluateByteCode(p.second.bytes, p.second.length); - } - -#if ENABLE_PROFILE - nativePerformance.mark(PERF_JS_POLYFILL_INIT_END); -#endif -} - -bool KrakenPage::parseHTML(const char* code, size_t length) { - if (!m_context->isValid()) - return false; - - ElementInstance* documentElement = m_context->document()->getDocumentElement(); - JSContext* ctx = m_context->ctx(); - int32_t len = arrayGetLength(ctx, documentElement->childNodes); - - // Remove all Nodes including body and head. - if (documentElement != nullptr) { - for (int i = len - 1; i >= 0; i--) { - JSValue v = JS_GetPropertyUint32(ctx, documentElement->childNodes, i); - auto* nodeInstance = static_cast<NodeInstance*>(JS_GetOpaque(v, Node::classId(v))); - if (nodeInstance->nodeType == NodeType::ELEMENT_NODE) { - documentElement->internalRemoveChild(nodeInstance); - } - JS_FreeValue(ctx, v); - } - - JS_FreeValue(ctx, documentElement->jsObject); - } - - HTMLParser::parseHTML(code, length, documentElement); - - return true; -} - -void KrakenPage::invokeModuleEvent(NativeString* moduleName, const char* eventType, void* rawEvent, NativeString* extra) { - if (!m_context->isValid()) - return; - - JSValue eventObject = JS_NULL; - if (rawEvent != nullptr) { - std::string type = std::string(eventType); - auto* event = static_cast<RawEvent*>(rawEvent)->bytes; - EventInstance* eventInstance = Event::buildEventInstance(type, m_context, event, false); - eventObject = eventInstance->jsObject; - } - - JSValue moduleNameValue = JS_NewUnicodeString(m_context->runtime(), m_context->ctx(), moduleName->string, moduleName->length); - JSValue extraObject = JS_NULL; - if (extra != nullptr) { - std::u16string u16Extra = std::u16string(reinterpret_cast<const char16_t*>(extra->string), extra->length); - std::string extraString = toUTF8(u16Extra); - extraObject = JS_ParseJSON(m_context->ctx(), extraString.c_str(), extraString.size(), ""); - } - - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &m_context->module_job_list) { - auto* module = list_entry(el, ModuleContext, link); - JSValue callback = module->callback; - - JSValue arguments[] = {moduleNameValue, eventObject, extraObject}; - JSValue returnValue = JS_Call(m_context->ctx(), callback, m_context->global(), 3, arguments); - m_context->handleException(&returnValue); - JS_FreeValue(m_context->ctx(), returnValue); - } - } - - JS_FreeValue(m_context->ctx(), moduleNameValue); - - if (rawEvent != nullptr) { - JS_FreeValue(m_context->ctx(), eventObject); - } - if (extra != nullptr) { - JS_FreeValue(m_context->ctx(), extraObject); - } -} - -void KrakenPage::evaluateScript(const NativeString* script, const char* url, int startLine) { - if (!m_context->isValid()) - return; - -#if ENABLE_PROFILE - auto nativePerformance = Performance::instance(m_context)->m_nativePerformance; - nativePerformance.mark(PERF_JS_PARSE_TIME_START); - std::u16string patchedCode = std::u16string(u"performance.mark('js_parse_time_end');") + std::u16string(reinterpret_cast<const char16_t*>(script->string), script->length); - m_context->evaluateJavaScript(patchedCode.c_str(), patchedCode.size(), url, startLine); -#else - m_context->evaluateJavaScript(script->string, script->length, url, startLine); -#endif -} - -void KrakenPage::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { - if (!m_context->isValid()) - return; - m_context->evaluateJavaScript(script, length, url, startLine); -} - -void KrakenPage::evaluateScript(const char* script, size_t length, const char* url, int startLine) { - if (!m_context->isValid()) - return; - m_context->evaluateJavaScript(script, length, url, startLine); -} - -uint8_t* KrakenPage::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { - if (!m_context->isValid()) - return nullptr; - return m_context->dumpByteCode(script, length, url, byteLength); -} - -void KrakenPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { - if (!m_context->isValid()) - return; - m_context->evaluateByteCode(bytes, byteLength); -} - -KrakenPage::~KrakenPage() { -#if IS_TEST - if (disposeCallback != nullptr) { - disposeCallback(this); - } -#endif - delete m_context; - KrakenPage::pageContextPool[contextId] = nullptr; -} - -void KrakenPage::reportError(const char* errmsg) { - m_handler(m_context->getContextId(), errmsg); -} - -} // namespace kraken diff --git a/bridge/page_test.cc b/bridge/page_test.cc deleted file mode 100644 index 3b33757b9f..0000000000 --- a/bridge/page_test.cc +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2020-present The Kraken authors. All rights reserved. - */ - -#include "page_test.h" -#include "bindings/qjs/bom/blob.h" -#include "testframework.h" - -namespace kraken { - -bool KrakenPageTest::evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { - if (!m_page_context->isValid()) - return false; - return m_page_context->evaluateJavaScript(code, codeLength, sourceURL, startLine); -} - -bool KrakenPageTest::parseTestHTML(const uint16_t* code, size_t codeLength) { - if (!m_page_context->isValid()) - return false; - std::string utf8Code = toUTF8(std::u16string(reinterpret_cast<const char16_t*>(code), codeLength)); - return m_page->parseHTML(utf8Code.c_str(), utf8Code.length()); -} - -static JSValue executeTest(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - JSValue& callback = argv[0]; - auto context = static_cast<binding::qjs::ExecutionContext*>(JS_GetContextOpaque(ctx)); - if (!JS_IsObject(callback)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'executeTest': parameter 1 (callback) is not an function."); - } - - if (!JS_IsFunction(ctx, callback)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'executeTest': parameter 1 (callback) is not an function."); - } - auto bridge = static_cast<KrakenPage*>(context->getOwner()); - auto bridgeTest = static_cast<KrakenPageTest*>(bridge->owner); - JS_DupValue(ctx, callback); - bridgeTest->executeTestCallback = callback; - return JS_NULL; -} - -static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - JSValue& blobValue = argv[0]; - JSValue& screenShotValue = argv[1]; - JSValue& callbackValue = argv[2]; - auto* context = static_cast<binding::qjs::ExecutionContext*>(JS_GetContextOpaque(ctx)); - - if (!JS_IsObject(blobValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); - } - auto blob = static_cast<kraken::binding::qjs::BlobInstance*>(JS_GetOpaque(blobValue, kraken::binding::qjs::Blob::kBlobClassID)); - - if (blob == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); - } - - if (!JS_IsString(screenShotValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 2 (match) must be an string."); - } - - if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); - } - - if (!JS_IsFunction(ctx, callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); - } - - if (getDartMethod()->matchImageSnapshot == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); - } - - std::unique_ptr<NativeString> screenShotNativeString = kraken::binding::qjs::jsValueToNativeString(ctx, screenShotValue); - auto bridge = static_cast<KrakenPageTest*>(static_cast<KrakenPage*>(context->getOwner())->owner); - auto* callbackContext = new ImageSnapShotContext{JS_DupValue(ctx, callbackValue), context}; - list_add_tail(&callbackContext->link, &bridge->image_link); - - auto fn = [](void* ptr, int32_t contextId, int8_t result, const char* errmsg) { - auto* callbackContext = static_cast<ImageSnapShotContext*>(ptr); - JSContext* ctx = callbackContext->context->ctx(); - - if (errmsg == nullptr) { - JSValue arguments[] = {JS_NewBool(ctx, result != 0), JS_NULL}; - JSValue returnValue = JS_Call(ctx, callbackContext->callback, callbackContext->context->global(), 1, arguments); - callbackContext->context->handleException(&returnValue); - } else { - JSValue errmsgValue = JS_NewString(ctx, errmsg); - JSValue arguments[] = {JS_NewBool(ctx, false), errmsgValue}; - JSValue returnValue = JS_Call(ctx, callbackContext->callback, callbackContext->context->global(), 2, arguments); - callbackContext->context->handleException(&returnValue); - JS_FreeValue(ctx, errmsgValue); - } - - callbackContext->context->drainPendingPromiseJobs(); - JS_FreeValue(callbackContext->context->ctx(), callbackContext->callback); - list_del(&callbackContext->link); - }; - - getDartMethod()->matchImageSnapshot(callbackContext, context->getContextId(), blob->bytes(), blob->size(), screenShotNativeString.get(), fn); - return JS_NULL; -} - -static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { -#if FLUTTER_BACKEND - if (getDartMethod()->environment == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_environment__': dart method (environment) is not registered."); - } - const char* env = getDartMethod()->environment(); - return JS_ParseJSON(ctx, env, strlen(env), ""); -#else - return JS_NewObject(ctx); -#endif -} - -static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (getDartMethod()->simulatePointer == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); - } - - auto* context = static_cast<binding::qjs::ExecutionContext*>(JS_GetContextOpaque(ctx)); - - JSValue inputArrayValue = argv[0]; - if (!JS_IsObject(inputArrayValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); - } - - JSValue pointerValue = argv[1]; - if (!JS_IsNumber(pointerValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); - } - - uint32_t length; - JSValue lengthValue = JS_GetPropertyStr(ctx, inputArrayValue, "length"); - JS_ToUint32(ctx, &length, lengthValue); - JS_FreeValue(ctx, lengthValue); - - auto** mousePointerList = new MousePointer*[length]; - - for (int i = 0; i < length; i++) { - auto mouse = new MousePointer(); - JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); - mouse->contextId = context->getContextId(); - JSValue xValue = JS_GetPropertyUint32(ctx, params, 0); - JSValue yValue = JS_GetPropertyUint32(ctx, params, 1); - JSValue changeValue = JS_GetPropertyUint32(ctx, params, 2); - - double x; - double y; - double change; - - JS_ToFloat64(ctx, &x, xValue); - JS_ToFloat64(ctx, &y, yValue); - JS_ToFloat64(ctx, &change, changeValue); - - mouse->x = x; - mouse->y = y; - mouse->change = change; - mousePointerList[i] = mouse; - - JS_FreeValue(ctx, params); - JS_FreeValue(ctx, xValue); - JS_FreeValue(ctx, yValue); - JS_FreeValue(ctx, changeValue); - } - - uint32_t pointer; - JS_ToUint32(ctx, &pointer, pointerValue); - - getDartMethod()->simulatePointer(mousePointerList, length, pointer); - - delete[] mousePointerList; - - return JS_NULL; -} - -static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (getDartMethod()->simulateInputText == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); - } - - JSValue& charStringValue = argv[0]; - - if (!JS_IsString(charStringValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); - } - - std::unique_ptr<NativeString> nativeString = kraken::binding::qjs::jsValueToNativeString(ctx, charStringValue); - getDartMethod()->simulateInputText(nativeString.get()); - nativeString->free(); - return JS_NULL; -}; - -static JSValue parseHTML(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* context = static_cast<binding::qjs::ExecutionContext*>(JS_GetContextOpaque(ctx)); - - if (argc == 1) { - JSValue& html = argv[0]; - - std::string strHTML = binding::qjs::jsValueToStdString(ctx, html); - - JSValue bodyValue = JS_GetPropertyStr(context->ctx(), context->document()->jsObject, "body"); - auto* body = static_cast<binding::qjs::ElementInstance*>(JS_GetOpaque(bodyValue, binding::qjs::Element::classId())); - binding::qjs::HTMLParser::parseHTML(strHTML, body); - - JS_FreeValue(ctx, bodyValue); - } - - return JS_NULL; -} - -static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* context = static_cast<binding::qjs::ExecutionContext*>(JS_GetContextOpaque(ctx)); - - JSValue globalErrorFunc = JS_GetPropertyStr(ctx, context->global(), "triggerGlobalError"); - - if (JS_IsFunction(ctx, globalErrorFunc)) { - JSValue exception = JS_Call(ctx, globalErrorFunc, context->global(), 0, nullptr); - context->handleException(&exception); - JS_FreeValue(ctx, globalErrorFunc); - } - - return JS_NULL; -} - -KrakenPageTest::KrakenPageTest(KrakenPage* bridge) : m_page(bridge), m_page_context(bridge->getContext()) { - bridge->owner = this; - bridge->disposeCallback = [](KrakenPage* bridge) { delete static_cast<KrakenPageTest*>(bridge->owner); }; - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__kraken_execute_test__", 1); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, matchImageSnapshot, "__kraken_match_image_snapshot__", 3); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, environment, "__kraken_environment__", 0); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulatePointer, "__kraken_simulate_pointer__", 1); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulateInputText, "__kraken_simulate_inputtext__", 1); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, triggerGlobalError, "__kraken_trigger_global_error__", 0); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, parseHTML, "__kraken_parse_html__", 1); - - initKrakenTestFramework(bridge); - init_list_head(&image_link); -} - -struct ExecuteCallbackContext { - ExecuteCallbackContext() = delete; - - explicit ExecuteCallbackContext(binding::qjs::ExecutionContext* context, ExecuteCallback executeCallback) : executeCallback(executeCallback), context(context){}; - ExecuteCallback executeCallback; - binding::qjs::ExecutionContext* context; -}; - -void KrakenPageTest::invokeExecuteTest(ExecuteCallback executeCallback) { - if (JS_IsNull(executeTestCallback)) { - return; - } - if (!JS_IsFunction(m_page_context->ctx(), executeTestCallback)) { - return; - } - - auto done = [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) -> JSValue { - JSValue& statusValue = argv[0]; - JSValue proxyObject = func_data[0]; - auto* callbackContext = static_cast<ExecuteCallbackContext*>(JS_GetOpaque(proxyObject, 1)); - - if (!JS_IsString(statusValue)) { - return JS_ThrowTypeError(ctx, "failed to execute 'done': parameter 1 (status) is not a string"); - } - - std::unique_ptr<NativeString> status = kraken::binding::qjs::jsValueToNativeString(ctx, statusValue); - callbackContext->executeCallback(callbackContext->context->getContextId(), status.get()); - return JS_NULL; - }; - auto* callbackContext = new ExecuteCallbackContext(m_page_context, executeCallback); - executeTestProxyObject = JS_NewObject(m_page_context->ctx()); - JS_SetOpaque(executeTestProxyObject, callbackContext); - JSValue callbackData[]{executeTestProxyObject}; - JSValue callback = JS_NewCFunctionData(m_page_context->ctx(), done, 0, 0, 1, callbackData); - - JSValue arguments[] = {callback}; - JSValue result = JS_Call(m_page_context->ctx(), executeTestCallback, executeTestCallback, 1, arguments); - m_page_context->handleException(&result); - m_page_context->drainPendingPromiseJobs(); - JS_FreeValue(m_page_context->ctx(), executeTestCallback); - JS_FreeValue(m_page_context->ctx(), callback); - executeTestCallback = JS_NULL; -} - -} // namespace kraken diff --git a/bridge/page_test.h b/bridge/page_test.h deleted file mode 100644 index c2f186c80e..0000000000 --- a/bridge/page_test.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2020-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_PAGE_TEST_H -#define KRAKENBRIDGE_PAGE_TEST_H - -#include "bindings/qjs/dom/document.h" -#include "bindings/qjs/html_parser.h" -#include "kraken_bridge_test.h" -#include "page.h" - -namespace kraken { - -struct ImageSnapShotContext { - JSValue callback; - binding::qjs::ExecutionContext* context; - list_head link; -}; - -class KrakenPageTest final { - public: - explicit KrakenPageTest() = delete; - explicit KrakenPageTest(KrakenPage* bridge); - - ~KrakenPageTest() { - if (!JS_IsNull(executeTestCallback)) { - JS_FreeValue(m_page_context->ctx(), executeTestCallback); - } - if (!JS_IsNull(executeTestProxyObject)) { - JS_FreeValue(m_page_context->ctx(), executeTestProxyObject); - } - - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &image_link) { - auto* image = list_entry(el, ImageSnapShotContext, link); - JS_FreeValue(m_page_context->ctx(), image->callback); - } - } - } - - /// evaluete JavaScript source code with build-in test frameworks, use in test only. - bool evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine); - bool parseTestHTML(const uint16_t* code, size_t codeLength); - void invokeExecuteTest(ExecuteCallback executeCallback); - - JSValue executeTestCallback{JS_NULL}; - JSValue executeTestProxyObject{JS_NULL}; - list_head image_link; - - private: - /// the pointer of bridge, ownership belongs to JSBridge - KrakenPage* m_page; - /// the pointer of JSContext, overship belongs to JSContext - binding::qjs::ExecutionContext* m_page_context; -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_PAGE_TEST_H diff --git a/bridge/polyfill/scripts/js_to_c.js b/bridge/polyfill/scripts/js_to_c.js index c25cbc873e..673fb606da 100644 --- a/bridge/polyfill/scripts/js_to_c.js +++ b/bridge/polyfill/scripts/js_to_c.js @@ -32,13 +32,9 @@ const getPolyFillHeader = (outputName) => `/* #ifndef KRAKEN_${outputName.toUpperCase()}_H #define KRAKEN_${outputName.toUpperCase()}_H -#if KRAKEN_JSC_ENGINE -#include "bridge_jsc.h" -#elif KRAKEN_QUICK_JS_ENGINE -#include "page.h" -#endif +#include "core/executing_context.h" -void initKraken${outputName}(kraken::KrakenPage *page); +void initKraken${outputName}(kraken::ExecutingContext *context); #endif // KRAKEN_${outputName.toUpperCase()}_H `; @@ -53,7 +49,7 @@ uint8_t bytes[${uint8Array.length}] = {${uint8Array.join(',')}}; }`; }; const getPolyfillEvalCall = () => { - return 'page->evaluateByteCode(bytes, byteLength);'; + return 'context->EvaluateByteCode(bytes, byteLength);'; } const getPolyFillSource = (source, outputName) => `/* @@ -64,14 +60,14 @@ const getPolyFillSource = (source, outputName) => `/* ${getPolyFillJavaScriptSource(source)} -void initKraken${outputName}(kraken::KrakenPage *page) { +void initKraken${outputName}(kraken::ExecutingContext *context) { ${getPolyfillEvalCall()} } -`; + `; -function convertJSToCpp(code, outputName) { - return getPolyFillSource(code, outputName); -} + function convertJSToCpp(code, outputName) { + return getPolyFillSource(code, outputName); + } let source = argv.s; let output = argv.o; diff --git a/bridge/polyfill/src/bridge.ts b/bridge/polyfill/src/bridge.ts index 6d6e681b05..7d946d7928 100644 --- a/bridge/polyfill/src/bridge.ts +++ b/bridge/polyfill/src/bridge.ts @@ -19,8 +19,11 @@ export interface PrivateKraken { declare const __kraken_invoke_module__: (module: string, method: string, params?: Object | null, fn?: (err: Error, data: any) => void) => string; export const krakenInvokeModule = __kraken_invoke_module__; -declare const __kraken_module_listener__: (fn: (moduleName: string, event: Event, extra: string) => void) => void; -export const addKrakenModuleListener = __kraken_module_listener__; +declare const __kraken_add_module_listener__: (fn: (moduleName: string, event: Event, extra: string) => void) => void; +export const addKrakenModuleListener = __kraken_add_module_listener__; + +declare const __kraken_location_reload__: () => void; +export const krakenLocationReload = __kraken_location_reload__; declare const __kraken_print__: (log: string, level?: string) => void; export const krakenPrint = __kraken_print__; diff --git a/bridge/polyfill/src/dom.ts b/bridge/polyfill/src/dom.ts index 310f383153..b5e795c4a5 100644 --- a/bridge/polyfill/src/dom.ts +++ b/bridge/polyfill/src/dom.ts @@ -1,6 +1,3 @@ -let html = document.createElement('html'); -document.appendChild(html); - let head = document.createElement('head'); document.documentElement.appendChild(head); @@ -8,15 +5,15 @@ let body = document.createElement('body'); document.documentElement.appendChild(body); // @ts-ignore -class SVGElement extends Element { - constructor() { - super(); - } -} - -Object.defineProperty(window, 'SVGElement', { - value: SVGElement -}); +// class SVGElement extends Element { +// constructor() { +// super(); +// } +// } +// +// Object.defineProperty(window, 'SVGElement', { +// value: SVGElement +// }); // Polyfill for document.getElementsByName // https://html.spec.whatwg.org/multipage/dom.html#dom-document-getelementsbyname diff --git a/bridge/polyfill/src/index.ts b/bridge/polyfill/src/index.ts index ab7b4a2ac7..7050fb5692 100644 --- a/bridge/polyfill/src/index.ts +++ b/bridge/polyfill/src/index.ts @@ -1,36 +1,35 @@ -import 'es6-promise/dist/es6-promise.auto'; import './dom'; -import './query-selector'; +// import './query-selector'; import { console } from './console'; -import { fetch, Request, Response, Headers } from './fetch'; -import { matchMedia } from './match-media'; +// import { fetch, Request, Response, Headers } from './fetch'; +// import { matchMedia } from './match-media'; import { location } from './location'; -import { history } from './history'; -import { navigator } from './navigator'; -import { XMLHttpRequest } from './xhr'; -import { asyncStorage } from './async-storage'; -import { URLSearchParams } from './url-search-params'; -import { URL } from './url'; +// import { history } from './history'; +// import { navigator } from './navigator'; +// import { XMLHttpRequest } from './xhr'; +// import { asyncStorage } from './async-storage'; +// import { URLSearchParams } from './url-search-params'; +// import { URL } from './url'; import { kraken } from './kraken'; -import { ErrorEvent, PromiseRejectionEvent } from './events'; +// import { ErrorEvent, PromiseRejectionEvent } from './events'; -defineGlobalProperty('ErrorEvent', ErrorEvent); -defineGlobalProperty('PromiseRejectionEvent', PromiseRejectionEvent); +// defineGlobalProperty('ErrorEvent', ErrorEvent); +// defineGlobalProperty('PromiseRejectionEvent', PromiseRejectionEvent); defineGlobalProperty('console', console); -defineGlobalProperty('Request', Request); -defineGlobalProperty('Response', Response); -defineGlobalProperty('Headers', Headers); -defineGlobalProperty('fetch', fetch); -defineGlobalProperty('matchMedia', matchMedia); +// defineGlobalProperty('Request', Request); +// defineGlobalProperty('Response', Response); +// defineGlobalProperty('Headers', Headers); +// defineGlobalProperty('fetch', fetch); +// defineGlobalProperty('matchMedia', matchMedia); defineGlobalProperty('location', location); -defineGlobalProperty('history', history); -defineGlobalProperty('navigator', navigator); -defineGlobalProperty('XMLHttpRequest', XMLHttpRequest); -defineGlobalProperty('asyncStorage', asyncStorage); -defineGlobalProperty('URLSearchParams', URLSearchParams); -defineGlobalProperty('URL', URL); +// defineGlobalProperty('history', history); +// defineGlobalProperty('navigator', navigator); +// defineGlobalProperty('XMLHttpRequest', XMLHttpRequest); +// defineGlobalProperty('asyncStorage', asyncStorage); +// defineGlobalProperty('URLSearchParams', URLSearchParams); +// defineGlobalProperty('URL', URL); defineGlobalProperty('kraken', kraken); -defineGlobalProperty('ErrorEvent', ErrorEvent); +// defineGlobalProperty('ErrorEvent', ErrorEvent); function defineGlobalProperty(key: string, value: any, isEnumerable: boolean = true) { Object.defineProperty(globalThis, key, { diff --git a/bridge/polyfill/src/location.ts b/bridge/polyfill/src/location.ts index 65319e9627..ff7b8b856a 100644 --- a/bridge/polyfill/src/location.ts +++ b/bridge/polyfill/src/location.ts @@ -1,15 +1,13 @@ import { URL } from './url'; +import { krakenLocationReload } from './bridge'; import { kraken } from './kraken'; -// @ts-ignore -const krakenLocation = window.__location__; // Lazy parse url. let _url: URL; export function getUrl() : URL { return _url ? _url : (_url = new URL(location.href)); } -const bindReload = krakenLocation.reload.bind(krakenLocation); export const location = { get href() { return kraken.invokeModule('Location', 'getHref'); @@ -48,7 +46,7 @@ export const location = { }; }, get reload() { - return bindReload; + return krakenLocationReload.bind(this); }, get replace() { return (replaceURL: string) => { diff --git a/bridge/polyfill/src/method-channel.ts b/bridge/polyfill/src/method-channel.ts index e93a9afafa..6b5b6a7285 100644 --- a/bridge/polyfill/src/method-channel.ts +++ b/bridge/polyfill/src/method-channel.ts @@ -22,13 +22,16 @@ export const methodChannel = { clearMethodCallHandler() { methodCallHandlers.length = 0; }, - invokeMethod(method: string, ...args: any[]): Promise<string> { - return new Promise((resolve, reject) => { - krakenInvokeModule('MethodChannel', 'invokeMethod', [method, args], (e, data) => { - if (e) return reject(e); - resolve(data); + invokeMethod(method: string, ...args: any[]): string { + // return new Promise((resolve, reject) => { + // krakenInvokeModule('MethodChannel', 'invokeMethod', [method, args], (e, data) => { + // if (e) return reject(e); + // resolve(data); + // }); + // }); + return krakenInvokeModule('MethodChannel', 'invokeMethod', [method, args], function aaa(e, data) { + console.log('1234'); }); - }); }, }; diff --git a/bridge/polyfill/src/test/index.js b/bridge/polyfill/src/test/index.js index 967b90ab86..1aaf3cd6d2 100644 --- a/bridge/polyfill/src/test/index.js +++ b/bridge/polyfill/src/test/index.js @@ -3,7 +3,6 @@ const ConsoleReporter = require('./console-reporter'); const jasmine = jasmineCore.core(jasmineCore); const env = jasmine.getEnv({ suppressLoadErrors: true }); const jasmineInterface = jasmineCore.interface(jasmine, env); -const environment = __kraken_environment__(); const global = globalThis; let timers = []; @@ -83,31 +82,9 @@ function createPrinter(logger) { } } -function HtmlSpecFilter(options) { - var filterString = - options && - options.filterString() && - options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); - var filterPattern = new RegExp(filterString); - - this.matches = function (specName) { - return filterPattern.test(specName); - }; -} - -var specFilter = new HtmlSpecFilter({ - filterString() { - return environment.KRAKEN_TEST_FILTER; - } -}); - let config = { oneFailurePerSpec: true, - failFast: environment.KRAKEN_STOP_ON_FAIL === 'true', - random: false, - specFilter: function (spec) { - return specFilter.matches(spec.getFullName()); - } + random: false }; env.configure(config); @@ -158,9 +135,10 @@ global.simulateInputText = __kraken_simulate_inputtext__; function resetDocumentElement() { window.scrollTo(0, 0); - document.removeChild(document.documentElement); - let html = document.createElement('html'); - document.appendChild(html); + + while(document.documentElement.firstChild) { + document.documentElement.firstChild.remove(); + } let head = document.createElement('head'); document.documentElement.appendChild(head); diff --git a/bridge/polyfill/src/test/jasmine.js b/bridge/polyfill/src/test/jasmine.js index 84f444c0a9..ce1537db36 100644 --- a/bridge/polyfill/src/test/jasmine.js +++ b/bridge/polyfill/src/test/jasmine.js @@ -2764,9 +2764,9 @@ getJasmineRequireObj().clearStack = function (j$) { getJasmineRequireObj().Clock = function () { /* global process */ var NODE_JS = - typeof process !== 'undefined' && - process.versions && - typeof process.versions.node === 'string'; + typeof Process !== 'undefined' && + Process.versions && + typeof Process.versions.node === 'string'; /** * _Note:_ Do not construct this directly, Jasmine will make one during booting. You can get the current clock with {@link jasmine.clock}. @@ -3727,22 +3727,22 @@ getJasmineRequireObj().GlobalErrors = function (j$) { } } - this.originalHandlers[errorType] = global.process.listeners(errorType); + this.originalHandlers[errorType] = global.Process.listeners(errorType); this.jasmineHandlers[errorType] = taggedOnError; - global.process.removeAllListeners(errorType); - global.process.on(errorType, taggedOnError); + global.Process.removeAllListeners(errorType); + global.Process.on(errorType, taggedOnError); this.uninstall = function uninstall() { var errorTypes = Object.keys(this.originalHandlers); for (var iType = 0; iType < errorTypes.length; iType++) { var errorType = errorTypes[iType]; - global.process.removeListener( + global.Process.removeListener( errorType, this.jasmineHandlers[errorType] ); for (var i = 0; i < this.originalHandlers[errorType].length; i++) { - global.process.on(errorType, this.originalHandlers[errorType][i]); + global.Process.on(errorType, this.originalHandlers[errorType][i]); } delete this.originalHandlers[errorType]; delete this.jasmineHandlers[errorType]; @@ -3752,9 +3752,9 @@ getJasmineRequireObj().GlobalErrors = function (j$) { this.install = function install() { if ( - global.process && - global.process.listeners && - j$.isFunction_(global.process.on) + global.Process && + global.Process.listeners && + j$.isFunction_(global.Process.on) ) { this.installOne_('uncaughtException', 'Uncaught exception'); this.installOne_('unhandledRejection', 'Unhandled promise rejection'); diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index b0b7dbe531..a9ab44b832 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -5,8 +5,11 @@ const packageJSON = require('../package.json'); const path = require('path'); const glob = require('glob'); const fs = require('fs'); -const { Blob } = require('../dist/blob'); -const { analyzer } = require('../dist/analyzer'); +const { IDLBlob } = require('../dist/idl/IDLBlob'); +const { JSONBlob } = require('../dist/json/JSONBlob'); +const { JSONTemplate } = require('../dist/json/JSONTemplate'); +const { analyzer } = require('../dist/idl/analyzer'); +const { generateJSONTemplate } = require('../dist/json/generator'); program .version(packageJSON.version) @@ -25,24 +28,66 @@ if (!path.isAbsolute(dist)) { dist = path.join(process.cwd(), dist); } -let files = glob.sync("**/*.d.ts", { - cwd: source, -}); +function genCodeFromTypeDefine() { + // Generate code from type defines. + let typeFiles = glob.sync("**/*.d.ts", { + cwd: source, + }); -let blobs = files.map(file => { - let filename = file.replace('.d.ts', ''); - return new Blob(path.join(source, file), dist, filename); -}); + let blobs = typeFiles.map(file => { + let filename = 'qjs_' + file.split('/').slice(-1)[0].replace('.d.ts', ''); + let implement = file.replace(path.join(__dirname, '../../')).replace('.d.ts', ''); + return new IDLBlob(path.join(source, file), dist, filename, implement); + }); -for (let i = 0; i < blobs.length; i ++) { - let b = blobs[i]; - let result = analyzer(b); + for (let i = 0; i < blobs.length; i ++) { + let b = blobs[i]; + let result = analyzer(b); - if (!fs.existsSync(b.dist)) { - fs.mkdirSync(b.dist); + if (!fs.existsSync(b.dist)) { + fs.mkdirSync(b.dist, {recursive: true}); + } + + let genFilePath = path.join(b.dist, b.filename); + + fs.writeFileSync(genFilePath + '.h', result.header); + fs.writeFileSync(genFilePath + '.cc', result.source); } +} + +// Generate code from json data. +function genCodeFromJSONData() { + let jsonFiles = glob.sync('**/*.json5', { + cwd: source + }); + let templateFiles = glob.sync('**/*.tpl', { + cwd: path.join(__dirname, '../static/json_templates') + }); - fs.writeFileSync(path.join(b.dist, b.filename) + '.h', result.header); - fs.writeFileSync(path.join(b.dist, b.filename) + '.cc', result.source); + let blobs = jsonFiles.map(file => { + let filename = file.split('/').slice(-1)[0].replace('.json', ''); + return new JSONBlob(path.join(source, file), dist, filename); + }); + + let templates = templateFiles.map(template => { + let filename = template.split('/').slice(-1)[0].replace('.tpl', ''); + return new JSONTemplate(path.join(path.join(__dirname, '../static/json_templates'), template), filename); + }); + + for (let i = 0; i < blobs.length; i ++) { + let blob = blobs[i]; + blob.json.metadata.templates.forEach((targetTemplate) => { + let targetTemplateHeaderData = templates.find(t => t.filename === targetTemplate.template + '.h'); + let targetTemplateBodyData = templates.find(t => t.filename === targetTemplate.template + '.cc'); + blob.filename = targetTemplate.filename; + let result = generateJSONTemplate(blobs[i], targetTemplateHeaderData, targetTemplateBodyData); + let dist = blob.dist; + let genFilePath = path.join(dist, targetTemplate.filename); + fs.writeFileSync(genFilePath + '.h', result.header); + result.source && fs.writeFileSync(genFilePath + '.cc', result.source); + }); + } } +genCodeFromTypeDefine(); +genCodeFromJSONData(); diff --git a/bridge/scripts/code_generator/global.d.ts b/bridge/scripts/code_generator/global.d.ts new file mode 100644 index 0000000000..7f631cb160 --- /dev/null +++ b/bridge/scripts/code_generator/global.d.ts @@ -0,0 +1,12 @@ +declare type int64 = number; +declare type double = number; + +declare interface Dictionary {} + +declare interface BlobPart {} +declare interface BlobPropertyBag {} +declare function Dictionary() : any; +declare type JSEventListener = void; + +// This property is implemented by Dart side +type DartImpl<T> = T; diff --git a/bridge/scripts/code_generator/package.json b/bridge/scripts/code_generator/package.json index 47c80250fd..9595178807 100644 --- a/bridge/scripts/code_generator/package.json +++ b/bridge/scripts/code_generator/package.json @@ -13,6 +13,7 @@ "@types/node": "^16.9.2", "commander": "^8.1.0", "glob": "^7.1.7", + "json5": "^2.2.1", "lodash": "^4.17.21", "typescript": "^4.3.5" } diff --git a/bridge/scripts/code_generator/src/analyzer.ts b/bridge/scripts/code_generator/src/analyzer.ts deleted file mode 100644 index 2334d96629..0000000000 --- a/bridge/scripts/code_generator/src/analyzer.ts +++ /dev/null @@ -1,155 +0,0 @@ -import ts, {HeritageClause, ScriptTarget} from 'typescript'; -import {Blob} from './blob'; -import { - ClassObject, - FunctionArguments, - FunctionArgumentType, - FunctionDeclaration, - PropsDeclaration, - PropsDeclarationKind -} from './declaration'; -import {generatorSource} from './generator'; - -export function analyzer(blob: Blob) { - let code = blob.raw; - const sourceFile = ts.createSourceFile(blob.source, blob.raw, ScriptTarget.ES2020); - blob.objects = sourceFile.statements.map(statement => walkProgram(statement)).filter(o => o instanceof ClassObject) as ClassObject[]; - return generatorSource(blob); -} - -function getInterfaceName(statement: ts.Statement) { - return (statement as ts.InterfaceDeclaration).name.escapedText; -} - -function getHeritageType(heritage: HeritageClause) { - let expression = heritage.types[0].expression; - if (expression.kind === ts.SyntaxKind.Identifier) { - return (expression as ts.Identifier).escapedText; - } - return null; -} - -function getPropKind(type: ts.TypeNode): PropsDeclarationKind { - if (type.kind === ts.SyntaxKind.StringKeyword) { - return PropsDeclarationKind.string; - } else if (type.kind === ts.SyntaxKind.NumberKeyword) { - return PropsDeclarationKind.double; - } else if (type.kind === ts.SyntaxKind.BooleanKeyword) { - return PropsDeclarationKind.boolean; - } else if (type.kind === ts.SyntaxKind.FunctionType) { - return PropsDeclarationKind.function; - } else if (type.kind === ts.SyntaxKind.TypeReference) { - // @ts-ignore - let typeName = (type as ts.TypeReference).typeName; - if (typeName.escapedText === 'int64') { - return PropsDeclarationKind.int64; - } - } - return PropsDeclarationKind.object; -} - -function getPropName(propName: ts.PropertyName) { - if (propName.kind == ts.SyntaxKind.Identifier) { - return propName.escapedText.toString(); - } else if (propName.kind === ts.SyntaxKind.StringLiteral) { - return propName.text; - } else if (propName.kind === ts.SyntaxKind.NumericLiteral) { - return propName.text; - } - throw new Error(`prop name: ${ts.SyntaxKind[propName.kind]} is not supported`); -} - -function getParameterName(name: ts.BindingName) : string { - if (name.kind === ts.SyntaxKind.Identifier) { - return name.escapedText.toString(); - } - return ''; -} - -function getParameterType(type: ts.TypeNode) { - if (type.kind === ts.SyntaxKind.StringKeyword) { - return FunctionArgumentType.string; - } else if (type.kind === ts.SyntaxKind.NumberKeyword) { - return FunctionArgumentType.number; - } else if (type.kind === ts.SyntaxKind.BooleanKeyword) { - return FunctionArgumentType.boolean; - } - return FunctionArgumentType.union; -} - -function paramsNodeToArguments(parameter: ts.ParameterDeclaration): FunctionArguments { - let args = new FunctionArguments(); - args.name = getParameterName(parameter.name); - args.type = getParameterType(parameter.type!); - args.required = !parameter.questionToken; - return args; -} - -function isParamsReadOnly(m: ts.PropertySignature): boolean { - if (!m.modifiers) return false; - return m.modifiers.some(k => k.kind === ts.SyntaxKind.ReadonlyKeyword); -} - -function walkProgram(statement: ts.Statement) { - switch(statement.kind) { - case ts.SyntaxKind.InterfaceDeclaration: { - let interfaceName = getInterfaceName(statement); - if (interfaceName === 'HostObject' || interfaceName === 'HostClass' || interfaceName === 'Element' || interfaceName === 'Event') return; - let s = (statement as ts.InterfaceDeclaration); - let obj = new ClassObject(); - if (s.heritageClauses) { - let heritage = s.heritageClauses[0]; - let heritageType = getHeritageType(heritage); - if (heritageType) obj.type = heritageType.toString(); - } - - obj.name = s.name.escapedText.toString(); - - s.members.forEach(member => { - switch(member.kind) { - case ts.SyntaxKind.PropertySignature: { - let prop = new PropsDeclaration(); - let m = (member as ts.PropertySignature); - prop.name = getPropName(m.name); - prop.readonly = isParamsReadOnly(m); - - let propKind = m.type; - if (propKind) { - prop.kind = getPropKind(propKind); - if (prop.kind === PropsDeclarationKind.function) { - let f = (m.type as ts.FunctionTypeNode); - let functionProps = prop as FunctionDeclaration; - functionProps.args = []; - f.parameters.forEach(params => { - let p = paramsNodeToArguments(params); - functionProps.args.push(p); - }); - obj.methods.push(functionProps); - } else { - obj.props.push(prop); - } - } - - break; - } - case ts.SyntaxKind.MethodSignature: { - let m = (member as ts.MethodSignature); - let f = new FunctionDeclaration(); - f.name = getPropName(m.name); - f.kind = PropsDeclarationKind.function; - f.args = []; - m.parameters.forEach(params => { - let p = paramsNodeToArguments(params); - f.args.push(p); - }); - obj.methods.push(f); - } - } - }); - - return obj; - } - } - - return null; -} diff --git a/bridge/scripts/code_generator/src/declaration.ts b/bridge/scripts/code_generator/src/declaration.ts deleted file mode 100644 index a88a480846..0000000000 --- a/bridge/scripts/code_generator/src/declaration.ts +++ /dev/null @@ -1,39 +0,0 @@ -export enum FunctionArgumentType { - string, - number, - boolean, - union -} - -export class FunctionArguments { - name: string; - type: FunctionArgumentType; - required: boolean; -} - -export enum PropsDeclarationKind { - none, - string, - double, - int64, - boolean, - object, - function -} - -export class PropsDeclaration { - kind: PropsDeclarationKind; - name: string; - readonly: boolean; -} - -export class FunctionDeclaration extends PropsDeclaration { - args: FunctionArguments[] -} - -export class ClassObject { - name: string; - type: string; - props: PropsDeclaration[] = []; - methods: FunctionDeclaration[] = []; -} diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts deleted file mode 100644 index 158b1e4a6c..0000000000 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ /dev/null @@ -1,181 +0,0 @@ -import {ClassObject, PropsDeclaration, PropsDeclarationKind} from "./declaration"; -import {uniqBy} from "lodash"; -import {Blob} from "./blob"; -import {addIndent} from "./utils"; - -function generatePropsHeader(object: ClassObject, type: PropType) { - let propsDefine = ''; - if (object.props.length > 0) { - - if (type == PropType.hostObject) { - for (let i = 0; i < object.props.length; i ++) { - let p = object.props[i]; - - if (p.readonly) { - propsDefine += `DEFINE_READONLY_PROPERTY(${p.name});\n`; - } else { - propsDefine += `DEFINE_PROPERTY(${p.name});\n`; - } - } - } else { - for (let i = 0; i < object.props.length; i ++) { - let p = object.props[i]; - if (p.readonly) { - propsDefine += `DEFINE_PROTOTYPE_READONLY_PROPERTY(${p.name});\n`; - } else { - propsDefine += `DEFINE_PROTOTYPE_PROPERTY(${p.name});\n`; - } - } - } - } - return propsDefine; -} - -enum PropType { - hostObject, - hostClass, -} - -function generateMethodsHeader(object: ClassObject, type: PropType) { - let methodsDefine: string[] = []; - let methodsImpl: string[] = []; - if (object.methods.length > 0) { - let methods = uniqBy(object.methods, (o) => o.name); - methodsDefine = methods.map(o => `static JSValue ${o.name}(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);`); - - if (type == PropType.hostClass) { - methodsImpl = methods.map(o => `DEFINE_PROTOTYPE_FUNCTION(${o.name}, ${o.args.length});`) - } else { - methodsImpl = methods.map(o => `DEFINE_FUNCTION(${o.name}, ${o.args.length});`) - } - } - return { - methodsImpl, - methodsDefine - } -} - -function generateHostObjectHeader(object: ClassObject) { - let propsDefine = generatePropsHeader(object, PropType.hostObject); - let {methodsImpl, methodsDefine} = generateMethodsHeader(object, PropType.hostObject); - - return `\n -struct Native${object.name} { - InvokeBindingMethod invokeBindingMethod{nullptr}; -}; - -class ${object.name} : public ${object.type} { -public: - ${object.name}() = delete; - explicit ${object.name}(ExecutionContext *context, Native${object.name} *nativePtr); - - JSValue invokeBindingMethod(const char* method, int32_t argc, - NativeValue *argv); - // @TODO: Should remove it. - JSValue getBindingProperty(const char* prop); - void setBindingProperty(const char* prop, NativeValue value); - - - ${methodsDefine.join('\n ')} - -private: - Native${object.name} *m_nativePtr{nullptr}; - ${propsDefine} - - ${methodsImpl.join('\n ')} -};`; -} - -function generateHostClassHeader(object: ClassObject) { - let {methodsImpl, methodsDefine} = generateMethodsHeader(object, PropType.hostClass); - let propsDefine = generatePropsHeader(object, PropType.hostClass); - - let nativeStructCode = ''; - - if (object.type === 'Event') { - let nativeStructPropsCode = object.props.map(p => { - switch(p.kind) { - case PropsDeclarationKind.object: - case PropsDeclarationKind.string: - return `NativeString *${p.name};`; - case PropsDeclarationKind.double: - return `double ${p.name};`; - case PropsDeclarationKind.int64: - case PropsDeclarationKind.boolean: - return `int64_t ${p.name};`; - } - return null; - }).filter(p => !!p); - - nativeStructCode = `struct Native${object.name} { - NativeEvent nativeEvent; -${addIndent(nativeStructPropsCode.join('\n'), 2)} -};`; - } - - let constructorHeader = `\n -void bind${object.name}(ExecutionContext *context); - -class ${object.name}Instance; - -${nativeStructCode} -class ${object.name} : public ${object.type} { -public: - ${object.name}() = delete; - explicit ${object.name}(ExecutionContext *context); - JSValue instanceConstructor(JSContext *ctx, JSValue func_obj, JSValue this_val, int argc, JSValue *argv) override; - ${methodsDefine.join('\n ')} - OBJECT_INSTANCE(${object.name}); -private: - ${propsDefine} - ${methodsImpl.join('\n ')} - friend ${object.name}Instance; -};`; - - let instanceConstructorHeader = ``; - if (object.type === 'Event') { - instanceConstructorHeader = `explicit ${object.name}Instance(${object.name} *${object.type.toLowerCase()}, NativeEvent *nativeEvent);`; - } else { - instanceConstructorHeader = `explicit ${object.name}Instance(${object.name} *${object.type.toLowerCase()});`; - } - - let instanceHeaders = `class ${object.name}Instance : public ${object.type}Instance { -public: - ${object.name}Instance() = delete; - ${instanceConstructorHeader} -private: - friend ${object.name}; -}; -`; - - return constructorHeader + '\n' + instanceHeaders; -} - -function generateObjectHeader(object: ClassObject) { - if (object.type === 'HostClass' || object.type === 'Element' || object.type === 'Event') { - return generateHostClassHeader(object); - } else if (object.type === 'HostObject') { - return generateHostObjectHeader(object); - } - return null; -} - -export function generateCppHeader(blob: Blob) { - let headers = blob.objects.map(o => generateObjectHeader(o)); - - return `/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_${blob.filename.toUpperCase()}_H -#define KRAKENBRIDGE_${blob.filename.toUpperCase()}_H - -#include "bindings/qjs/dom/element.h" - -namespace kraken::binding::qjs { -${headers.join('')} -} - -#endif //KRAKENBRIDGE_${blob.filename.toUpperCase()}T_H -`; -} diff --git a/bridge/scripts/code_generator/src/generator.ts b/bridge/scripts/code_generator/src/generator.ts deleted file mode 100644 index 661e2bb643..0000000000 --- a/bridge/scripts/code_generator/src/generator.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {Blob} from './blob'; -import {generateCppHeader} from "./generate_header"; -import {generateCppSource} from "./genereate_source"; - -export function generatorSource(blob: Blob) { - let header = generateCppHeader(blob); - let source = generateCppSource(blob); - return { - header, - source - }; -} diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts deleted file mode 100644 index 6b42b4ffc9..0000000000 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ /dev/null @@ -1,517 +0,0 @@ -import {Blob} from "./blob"; -import { - ClassObject, - FunctionArguments, - FunctionArgumentType, - FunctionDeclaration, - PropsDeclaration, - PropsDeclarationKind -} from "./declaration"; -import {addIndent} from "./utils"; - -function generateHostObjectSource(object: ClassObject) { - let propSource: string[] = generatePropsSource(object, PropType.hostObject); - let methodsSource: string[] = generateMethodsSource(object, PropType.hostObject); - return `${object.name}::${object.name}(ExecutionContext *context, - Native${object.name} *nativePtr) - : HostObject(context, "${object.name}"), m_nativePtr(nativePtr) { -} - -// @TODO: Should remove it. -JSValue ${object.name}::getBindingProperty(const char* prop) { - getDartMethod()->flushUICommand(); - NativeValue args[] = {Native_NewCString(prop)}; - return invokeBindingMethod(GetPropertyMagic, 1, args); -} -void ${object.name}::setBindingProperty(const char* prop, NativeValue value) { - // If not flush UICommands, the element may not be created. - getDartMethod()->flushUICommand(); - NativeValue args[] = {Native_NewCString(prop), value}; - invokeBindingMethod(SetPropertyMagic, 2, args); -} - -JSValue ${object.name}::invokeBindingMethod(const char *method, int32_t argc, - NativeValue *argv) { - if (m_nativePtr->invokeBindingMethod == nullptr) { - return JS_ThrowTypeError(m_ctx, "Failed to call native dart methods: invokeBindingMethod not initialized."); - } - - std::u16string methodString; - fromUTF8(method, methodString); - - NativeString m{ - reinterpret_cast<const uint16_t *>(methodString.c_str()), - static_cast<uint32_t>(methodString.size()) - }; - - NativeValue nativeValue{}; - m_nativePtr->invokeBindingMethod(m_nativePtr, &nativeValue, &m, argc, argv); - JSValue returnValue = nativeValueToJSValue(m_context, nativeValue); - return returnValue; -} -${propSource.join('\n')} -${methodsSource.join('\n')} -`; -} - -enum PropType { - hostObject, - Element, - Event -} - -function getPropsVars(object: ClassObject, type: PropType) { - let classSubFix = object.name; - let className = object.name; - if (type == PropType.Element || type == PropType.Event) { - classSubFix += 'Instance'; - } - - let instanceName = ''; - let classId = ''; - if (type == PropType.hostObject) { - instanceName = 'object'; - classId = 'ExecutionContext::kHostObjectClassId'; - } else if (type == PropType.Element) { - instanceName = 'element'; - classId = 'Element::classId()'; - } else if (type == PropType.Event) { - instanceName = 'event'; - classId = 'Event::kEventClassID'; - } - - return { - className, - classSubFix, - classId, - instanceName - }; -} - -function generatePropsGetter(object: ClassObject, type: PropType, p: PropsDeclaration) { - let { - classId, - classSubFix, - className, - instanceName - } = getPropsVars(object, type); - - - let getterCode = ''; - if (object.type === 'Event') { - let qjsCallFunc = ''; - if (p.kind === PropsDeclarationKind.double) { - qjsCallFunc = `return JS_NewFloat64(ctx, nativeEvent->${p.name})`; - } else if (p.kind === PropsDeclarationKind.boolean) { - qjsCallFunc = `return JS_NewBool(ctx, nativeEvent->${p.name} ? 1 : 0)`; - } else if (p.kind === PropsDeclarationKind.string) { - qjsCallFunc = `return JS_NewUnicodeString(event->m_context->runtime(), ctx, nativeEvent->${p.name}->string, nativeEvent->${p.name}->length);`; - } else if (p.kind === PropsDeclarationKind.int64) { - qjsCallFunc = `return JS_NewUint32(ctx, nativeEvent->${p.name});` - } else if (p.kind === PropsDeclarationKind.object) { - qjsCallFunc = `std::u16string u16${p.name} = std::u16string(reinterpret_cast<const char16_t *>(nativeEvent->${p.name}->string), nativeEvent->${p.name}->length); - std::string ${p.name} = toUTF8(u16${p.name}); - return JS_ParseJSON(ctx, ${p.name}.c_str(), ${p.name}.size(), "");`; - } - - getterCode = `auto *${instanceName} = static_cast<${classSubFix} *>(JS_GetOpaque(this_val, ${classId})); - auto *nativeEvent = reinterpret_cast<Native${object.name} *>(event->nativeEvent); - ${qjsCallFunc};`; - } else if (object.type === 'HostObject') { - getterCode = `auto *${instanceName} = static_cast<${classSubFix} *>(JS_GetOpaque(this_val, ${classId})); - return ${instanceName}->invokeBindingMethod("get${p.name[0].toUpperCase() + p.name.substring(1)}", 0, nullptr);`; - } else { - getterCode = `auto *${instanceName} = static_cast<${classSubFix} *>(JS_GetOpaque(this_val, ${classId})); - return ${instanceName}->getBindingProperty("${p.name}");`; - } - - let flushUICommandCode = ''; - if (object.type === 'Element' || object.type === 'HostObject') { - flushUICommandCode = 'getDartMethod()->flushUICommand();' - } - - return `IMPL_PROPERTY_GETTER(${className}, ${p.name})(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - ${flushUICommandCode} - ${getterCode} -}`; -} - -function generatePropsSetter(object: ClassObject, type: PropType, p: PropsDeclaration) { - let { - classId, - classSubFix, - className, - instanceName - } = getPropsVars(object, type); - - if (p.readonly) { - return ''; - } - - let setterCode = ''; - switch (p.kind) { - case PropsDeclarationKind.string: - setterCode = `getDartMethod()->flushUICommand(); - JSValue value = argv[0]; - if (JS_IsNull(value)) { - ${instanceName}->setBindingProperty("${p.name}", Native_NewNull()); - } else { - const char* stringValue = JS_ToCString(ctx, value); - ${instanceName}->setBindingProperty("${p.name}", Native_NewCString(stringValue)); - JS_FreeCString(ctx, stringValue); - } - return JS_DupValue(ctx, value);`; - break; - case PropsDeclarationKind.double: - setterCode = `getDartMethod()->flushUICommand(); - double floatValue = 0; - JSValue value = argv[0]; - JS_ToFloat64(ctx, &floatValue, value); - NativeValue nativeValue = Native_NewFloat64(floatValue); - ${instanceName}->setBindingProperty("${p.name}", nativeValue); - return JS_DupValue(ctx, value);`; - break; - case PropsDeclarationKind.int64: - setterCode = `getDartMethod()->flushUICommand(); - int32_t intValue = 0; - JSValue value = argv[0]; - JS_ToInt32(ctx, &intValue, value); - NativeValue nativeValue = Native_NewInt32(intValue); - ${instanceName}->setBindingProperty("${p.name}", nativeValue); - return JS_DupValue(ctx, value);`; - break; - case PropsDeclarationKind.boolean: - setterCode = `getDartMethod()->flushUICommand(); - JSValue value = argv[0]; - bool boolValue = JS_ToBool(ctx, value); - NativeValue nativeValue = Native_NewBool(boolValue); - ${instanceName}->setBindingProperty("${p.name}", nativeValue); - return JS_DupValue(ctx, value);`; - break; - case PropsDeclarationKind.object: - case PropsDeclarationKind.function: - default: - setterCode = `getDartMethod()->flushUICommand(); - JSValue value = argv[0]; - ${instanceName}->setBindingProperty("${p.name}", jsValueToNativeValue(ctx, value)); - return JS_DupValue(ctx, value);`; - break; - } - return `IMPL_PROPERTY_SETTER(${className}, ${p.name})(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - auto *${instanceName} = static_cast<${classSubFix} *>(JS_GetOpaque(this_val, ${classId})); - ${setterCode} -}`; -} - -function generatePropsSource(object: ClassObject, type: PropType) { - let propSource: string[] = []; - if (object.props.length > 0) { - object.props.forEach(p => { - let getter = generatePropsGetter(object, type, p); - let setter = generatePropsSetter(object, type, p); - propSource.push(getter + '\n' + setter); - }); - } - return propSource; -} - -function generateArgumentsTypeCheck(index: number, argv: FunctionArguments, m: FunctionDeclaration) { - if (argv.type == FunctionArgumentType.string) { - return `if (!JS_IsString(argv[${index}])) { - return JS_ThrowTypeError(ctx, "Failed to execute ${m.name}: ${index + 1}st arguments is not String."); - }`; - } else if (argv.type === FunctionArgumentType.number) { - return `if (!JS_IsNumber(argv[${index}])) { - return JS_ThrowTypeError(ctx, "Failed to execute ${m.name}: ${index + 1}st arguments is not Number."); - }` - } else if (argv.type === FunctionArgumentType.boolean) { - return `if (!JS_IsBool(argv[${index}])) { - return JS_ThrowTypeError(ctx, "Failed to execute ${m.name}: ${index + 1}st arguments is not Boolean."); - }` - } - - return ''; -} - -function generateMethodArgumentsCheck(m: FunctionDeclaration, object: ClassObject) { - if (m.args.length == 0) return ''; - - let requiredArgsCount = 0; - m.args.forEach(m => { - if (m.required) requiredArgsCount++; - }); - - let argsCheck: string[] = []; - for (let i = 0; i < requiredArgsCount; i++) { - argsCheck.push(generateArgumentsTypeCheck(i, m.args[i], m)); - } - - return ` if (argc < ${requiredArgsCount}) { - return JS_ThrowTypeError(ctx, "Failed to execute '${m.name}' on '${object.name}': ${requiredArgsCount} argument required, but %d present.", argc); - } - ${argsCheck.join('\n ')} -`; -} - -function generateDefaultNativeValue(m: FunctionArguments, index: number) { - switch(m.type) { - case FunctionArgumentType.boolean: - return `NativeValue argv${index} = Native_NewBool(false);`; - case FunctionArgumentType.number: - return `NativeValue argv${index} = Native_NewFloat64(NAN);`; - case FunctionArgumentType.string: - return `NativeValue argv${index} = Native_NewCString("");`; - default: - return ''; - } -} - -function generateMethodsSource(object: ClassObject, type: PropType) { - let { - classId, - classSubFix, - instanceName - } = getPropsVars(object, type); - - let methodsSource: string[] = []; - if (object.methods.length > 0) { - let methods = object.methods.slice(); - let polymorphismMap = {}; - methods.forEach((m) => { - let polymorphism = object.methods.filter(me => me.name === m.name).length > 1; - - if (polymorphismMap[m.name]) return; - polymorphismMap[m.name] = true; - - function createMethodBody(m: FunctionDeclaration) { - let callArgumentsCode = ''; - if (m.args.length > 0) { - let callArguments = []; - let optionalArguments = []; - for (let i = 0; i < m.args.length; i++) { - if (m.args[i].required) { - callArguments.push(` jsValueToNativeValue(ctx, argv[${i}])`); - } else { - optionalArguments.push(`${addIndent(generateDefaultNativeValue(m.args[i], i), 2)} - if (argc == ${i + 1}) { - argv${i} = jsValueToNativeValue(ctx, argv[${i}]); - }`); - callArguments.push(` argv${i}`); - } - } - callArgumentsCode = ` -${optionalArguments.join('\n ')} - NativeValue arguments[] = { - ${callArguments.join(',\n ')} - };`; - - - } - - return `${generateMethodArgumentsCheck(m, object)} - getDartMethod()->flushUICommand(); -${callArgumentsCode} - auto *${instanceName} = static_cast<${classSubFix} *>(JS_GetOpaque(this_val, ${classId})); - return ${instanceName}->invokeBindingMethod("${m.name}", ${m.args.length}, ${m.args.length > 0 ? 'arguments' : 'nullptr'});`; - } - - if (polymorphism) { - let allConditions = object.methods.filter(me => me.name === m.name); - let caseCode = []; - for (let i = 0; i < allConditions.length; i++) { - caseCode.push(`case ${allConditions[i].args.length}: { -${addIndent(createMethodBody(allConditions[i]), 2)} - }\n `) - } - - let polymorphismTemplate = `JSValue ${object.name}::${m.name}(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - switch(argc) { - ${addIndent(caseCode.join(''), 2)} - default: - return JS_NULL; - } -}`; - methodsSource.push(polymorphismTemplate); - } else { - let body = createMethodBody(m); - - methodsSource.push(`JSValue ${object.name}::${m.name}(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { -${body} -}`); - } - }); - } - - return methodsSource; -} - -function generateEventConstructorCode(object: ClassObject) { - return `if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to construct '${object.name}': 1 argument required, but only 0 present."); - } - - JSValue eventTypeValue = argv[0]; - JSValue eventInit = JS_NULL; - - if (argc == 2) { - eventInit = argv[1]; - } - - auto *nativeEvent = new Native${object.name}(); -#if ANDROID_32_BIT - nativeEvent->nativeEvent.type = reinterpret_cast<int64_t>(jsValueToNativeString(ctx, eventTypeValue).release()); -#else - nativeEvent->nativeEvent.type = jsValueToNativeString(ctx, eventTypeValue).release(); -#endif - - ${generateEventInstanceConstructorCode(object)} - - auto event = new ${object.name}Instance(this, reinterpret_cast<NativeEvent *>(nativeEvent)); - return event->jsObject;`; -} - -function generateEventInstanceConstructorCode(object: ClassObject) { - let atomCreateCode: string[] = []; - let atomReleaseCode: string[] = []; - let propWriteCode: string[] = []; - - object.props.forEach(p => { - atomCreateCode.push(`JSAtom ${p.name}Atom = JS_NewAtom(m_ctx, "${p.name}");`) - atomReleaseCode.push(`JS_FreeAtom(m_ctx, ${p.name}Atom);`) - - let propApplyCode = ''; - if (p.kind === PropsDeclarationKind.boolean) { - propApplyCode = `nativeEvent->${p.name} = JS_ToBool(m_ctx, JS_GetProperty(m_ctx, eventInit, ${p.name}Atom)) ? 1 : 0;`; - } else if (p.kind === PropsDeclarationKind.int64) { - propApplyCode = `JS_ToInt32(m_ctx, reinterpret_cast<int32_t *>(&nativeEvent->${p.name}), JS_GetProperty(m_ctx, eventInit, ${p.name}Atom));` - } else if (p.kind === PropsDeclarationKind.string) { - propApplyCode = addIndent(`JSValue v = JS_GetProperty(m_ctx, eventInit, ${p.name}Atom); - nativeEvent->${p.name} = jsValueToNativeString(m_ctx, v).release(); - JS_FreeValue(m_ctx, v);`, 0); - } else if (p.kind === PropsDeclarationKind.double) { - propApplyCode = `JS_ToFloat64(m_ctx, &nativeEvent->${p.name}, JS_GetProperty(m_ctx, eventInit, ${p.name}Atom));`; - } else if (p.kind === PropsDeclarationKind.object) { - propApplyCode = addIndent(`JSValue v = JS_GetProperty(m_ctx, eventInit, ${p.name}Atom); - JSValue json = JS_JSONStringify(m_ctx, v, JS_NULL, JS_NULL); - if (JS_IsException(json)) return json; - nativeEvent->${p.name} = jsValueToNativeString(m_ctx, json).release(); - JS_FreeValue(m_ctx, json); - JS_FreeValue(m_ctx, v);`, 0); - } - - propWriteCode.push(addIndent(`if (JS_HasProperty(m_ctx, eventInit, ${p.name}Atom)) { - ${propApplyCode} -}`, 4)); - }); - - return `if (JS_IsObject(eventInit)) { -${addIndent(atomCreateCode.join('\n'), 4)} - -${propWriteCode.join('\n')} - -${addIndent(atomReleaseCode.join('\n'), 4)} - }`; -} - -function elementNameToTagName(name: string): string { - switch(name) { - case 'AnchorElement': - return 'a'; - case 'CanvasElement': - return 'canvas'; - case 'ImageElement': - return 'img'; - case 'InputElement': - return 'input'; - case 'TextareaElement': - return 'textarea'; - case 'ObjectElement': - return 'object'; - case 'ScriptElement': - return 'script'; - case 'SvgElement': - return 'svg'; - } - return name; -} - -function generateHostClassSource(object: ClassObject) { - let propSource: string[] = generatePropsSource(object, object.type === 'Event' ? PropType.Event : PropType.Element); - let methodsSource: string[] = generateMethodsSource(object, object.type === 'Event' ? PropType.Event : PropType.Element); - let constructorCode = ''; - if (object.type === 'Element') { - constructorCode = `auto instance = new ${object.name}Instance(this); - return instance->jsObject;`; - } else if (object.type === 'Event') { - constructorCode = generateEventConstructorCode(object); - } - - let instanceConstructorCode = ''; - if (object.type === 'Event') { - instanceConstructorCode = `${object.name}Instance::${object.name}Instance(${object.name} *${object.type.toLowerCase()}, NativeEvent *nativeEvent): ${object.type}Instance(${object.type.toLowerCase()}, nativeEvent) {}` - } else { - instanceConstructorCode = `${object.name}Instance::${object.name}Instance(${object.name} *${object.type.toLowerCase()}): ${object.type}Instance(${object.type.toLowerCase()}, "${elementNameToTagName(object.name)}", true) {}`; - } - - let globalBindingName = ''; - if (object.type === 'Element') { - globalBindingName = `HTML${object.name}`; - } else { - globalBindingName = object.name; - } - - let specialBind = ''; - if (object.name === 'ImageElement') { - specialBind = `context->defineGlobalProperty("Image", JS_DupValue(context->ctx(), constructor->jsObject));` - } - - let classInheritCode = ''; - if (object.type === 'Element') { - classInheritCode = 'JS_SetPrototype(m_ctx, m_prototypeObject, Element::instance(m_context)->prototype());'; - } else if (object.type === 'Event') { - classInheritCode = 'JS_SetPrototype(m_ctx, m_prototypeObject, Event::instance(m_context)->prototype());'; - } - - return ` -${object.name}::${object.name}(ExecutionContext *context) : ${object.type}(context) { - ${classInheritCode} -} - -void bind${object.name}(ExecutionContext* context) { - auto *constructor = ${object.name}::instance(context); - context->defineGlobalProperty("${globalBindingName}", constructor->jsObject); - ${specialBind} -} - -JSValue ${object.name}::instanceConstructor(JSContext *ctx, JSValue func_obj, JSValue this_val, int argc, JSValue *argv) { - ${constructorCode} -} -${propSource.join('\n')} -${methodsSource.join('\n')} -${instanceConstructorCode} -`; -} - -function generateObjectSource(object: ClassObject) { - if (object.type === 'HostClass' || object.type === 'Element' || object.type === 'Event') { - return generateHostClassSource(object); - } else if (object.type === 'HostObject') { - return generateHostObjectSource(object); - } - return null; -} - -export function generateCppSource(blob: Blob) { - let sources = blob.objects.map(o => generateObjectSource(o)); - return `/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "${blob.filename}.h" -#include "page.h" -#include "bindings/qjs/qjs_patch.h" - -namespace kraken::binding::qjs { - ${sources.join('')} -}`; -} diff --git a/bridge/scripts/code_generator/src/idl/IDLBlob.ts b/bridge/scripts/code_generator/src/idl/IDLBlob.ts new file mode 100644 index 0000000000..da74425840 --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/IDLBlob.ts @@ -0,0 +1,19 @@ +import fs from 'fs'; +import {ClassObject, FunctionObject} from "./declaration"; + +export class IDLBlob { + raw: string; + dist: string; + source: string; + filename: string; + implement: string; + objects: (ClassObject | FunctionObject)[]; + + constructor(source: string, dist: string, filename: string, implement: string) { + this.source = source; + this.raw = fs.readFileSync(source, {encoding: 'utf-8'}); + this.dist = dist; + this.filename = filename; + this.implement = implement; + } +} diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts new file mode 100644 index 0000000000..e7023e525b --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -0,0 +1,250 @@ +import ts, {HeritageClause, ScriptTarget, VariableStatement} from 'typescript'; +import {IDLBlob} from './IDLBlob'; +import { + ClassObject, + ClassObjectKind, + FunctionArguments, + FunctionArgumentType, + FunctionDeclaration, + FunctionObject, IndexedPropertyDeclaration, ParameterMode, + PropsDeclaration, +} from './declaration'; +import {generatorSource} from './generator'; + +export function analyzer(blob: IDLBlob) { + let code = blob.raw; + const sourceFile = ts.createSourceFile(blob.source, blob.raw, ScriptTarget.ES2020); + blob.objects = sourceFile.statements.map(statement => walkProgram(statement)).filter(o => { + return o instanceof ClassObject || o instanceof FunctionObject; + }) as (FunctionObject | ClassObject)[]; + return generatorSource(blob); +} + +function getInterfaceName(statement: ts.Statement) { + return (statement as ts.InterfaceDeclaration).name.escapedText; +} + +function getHeritageType(heritage: HeritageClause) { + let expression = heritage.types[0].expression; + if (expression.kind === ts.SyntaxKind.Identifier) { + return (expression as ts.Identifier).escapedText; + } + return null; +} + +function getPropName(propName: ts.PropertyName) { + if (propName.kind == ts.SyntaxKind.Identifier) { + return propName.escapedText.toString(); + } else if (propName.kind === ts.SyntaxKind.StringLiteral) { + return propName.text; + } else if (propName.kind === ts.SyntaxKind.NumericLiteral) { + return propName.text; + } + throw new Error(`prop name: ${ts.SyntaxKind[propName.kind]} is not supported`); +} + +function getParameterName(name: ts.BindingName) : string { + if (name.kind === ts.SyntaxKind.Identifier) { + return name.escapedText.toString(); + } + return ''; +} + +export type ParameterType = FunctionArgumentType | string; + +function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): ParameterType { + if (type.kind === ts.SyntaxKind.StringKeyword) { + return FunctionArgumentType.dom_string; + } else if (type.kind === ts.SyntaxKind.NumberKeyword) { + return FunctionArgumentType.double; + } else if (type.kind === ts.SyntaxKind.BooleanKeyword) { + return FunctionArgumentType.boolean; + } else if (type.kind === ts.SyntaxKind.AnyKeyword) { + return FunctionArgumentType.any; + } else if (type.kind === ts.SyntaxKind.ObjectKeyword) { + return FunctionArgumentType.object; + // @ts-ignore + } else if (type.kind === ts.SyntaxKind.VoidKeyword) { + return FunctionArgumentType.void; + } else if (type.kind === ts.SyntaxKind.NullKeyword) { + return FunctionArgumentType.null; + } else if (type.kind === ts.SyntaxKind.UndefinedKeyword) { + return FunctionArgumentType.undefined; + } else if (type.kind === ts.SyntaxKind.TypeReference) { + let typeReference: ts.TypeReference = type as unknown as ts.TypeReference; + // @ts-ignore + let identifier = (typeReference.typeName as ts.Identifier).text; + if (identifier === 'Function') { + return FunctionArgumentType.function; + } else if (identifier === 'int32') { + return FunctionArgumentType.int32; + } else if (identifier === 'int64') { + return FunctionArgumentType.int64; + } else if (identifier === 'double') { + return FunctionArgumentType.double; + } else if (identifier === 'NewObject') { + if (mode) mode.newObject = true; + let argument = typeReference.typeArguments![0]; + // @ts-ignore + return argument.typeName.text; + } else if (identifier === 'DartImpl') { + if (mode) mode.dartImpl = true; + let argument = typeReference.typeArguments![0]; + // @ts-ignore + return getParameterBaseType(argument); + } + + return identifier; + } else if (type.kind === ts.SyntaxKind.LiteralType) { + // @ts-ignore + return getParameterBaseType((type as ts.LiteralTypeNode).literal, mode); + } + + return FunctionArgumentType.any; +} + +function getParameterType(type: ts.TypeNode, mode?: ParameterMode): ParameterType[] { + if (type.kind == ts.SyntaxKind.ArrayType) { + let arrayType = type as unknown as ts.ArrayTypeNode; + return [FunctionArgumentType.array, getParameterBaseType(arrayType.elementType, mode)]; + } else if (type.kind === ts.SyntaxKind.UnionType) { + let node = type as unknown as ts.UnionType; + let types = node.types; + // @ts-ignore + return types.map(type => getParameterBaseType(type as unknown as ts.TypeNode, mode)); + } + return [getParameterBaseType(type, mode)]; +} + +function paramsNodeToArguments(parameter: ts.ParameterDeclaration): FunctionArguments { + let args = new FunctionArguments(); + args.name = getParameterName(parameter.name); + args.type = getParameterType(parameter.type!); + args.required = !parameter.questionToken; + return args; +} + +function isParamsReadOnly(m: ts.PropertySignature): boolean { + if (!m.modifiers) return false; + return m.modifiers.some(k => k.kind === ts.SyntaxKind.ReadonlyKeyword); +} + +function walkProgram(statement: ts.Statement) { + switch(statement.kind) { + case ts.SyntaxKind.InterfaceDeclaration: { + let interfaceName = getInterfaceName(statement); + let s = (statement as ts.InterfaceDeclaration); + let obj = new ClassObject(); + if (s.heritageClauses) { + let heritage = s.heritageClauses[0]; + let heritageType = getHeritageType(heritage); + if (heritageType) obj.parent = heritageType.toString(); + } + + obj.name = s.name.escapedText.toString(); + + if (s.decorators) { + let decoratorExpression = s.decorators[0].expression as ts.CallExpression; + // @ts-ignore + if (decoratorExpression.expression.kind === ts.SyntaxKind.Identifier && decoratorExpression.expression.escapedText === 'Dictionary') { + obj.kind = ClassObjectKind.dictionary; + } + } + + s.members.forEach(member => { + switch(member.kind) { + case ts.SyntaxKind.PropertySignature: { + let prop = new PropsDeclaration(); + let m = (member as ts.PropertySignature); + prop.name = getPropName(m.name); + prop.readonly = isParamsReadOnly(m); + + let propKind = m.type; + if (propKind) { + let mode = new ParameterMode(); + prop.type = getParameterType(propKind, mode); + prop.typeMode = mode; + if (prop.type[0] === FunctionArgumentType.function) { + let f = (m.type as ts.FunctionTypeNode); + let functionProps = prop as FunctionDeclaration; + functionProps.args = []; + f.parameters.forEach(params => { + let p = paramsNodeToArguments(params); + functionProps.args.push(p); + }); + obj.methods.push(functionProps); + } else { + obj.props.push(prop); + } + } + break; + } + case ts.SyntaxKind.MethodSignature: { + let m = (member as ts.MethodSignature); + let f = new FunctionDeclaration(); + f.name = getPropName(m.name); + f.args = []; + m.parameters.forEach(params => { + let p = paramsNodeToArguments(params); + f.args.push(p); + }); + obj.methods.push(f); + if (m.type) { + let mode = new ParameterMode(); + f.returnType = getParameterType(m.type, mode); + f.returnTypeMode = mode; + } + break; + } + case ts.SyntaxKind.IndexSignature: { + let m = (member as ts.IndexSignatureDeclaration); + let prop = new IndexedPropertyDeclaration(); + let modifier = m.modifiers; + prop.readonly = !!(modifier && modifier[0].kind == ts.SyntaxKind.ReadonlyKeyword); + + let params = m.parameters; + prop.indexKeyType = params[0].type!.kind === ts.SyntaxKind.NumberKeyword ? 'number' : 'string'; + + let mode = new ParameterMode(); + prop.type = getParameterType(m.type, mode); + prop.typeMode = mode; + obj.indexedProp = prop; + break; + } + case ts.SyntaxKind.ConstructSignature: { + let m = (member as unknown as ts.ConstructorTypeNode); + let c = new FunctionDeclaration(); + c.name = 'constructor'; + c.args = []; + m.parameters.forEach(params => { + let p = paramsNodeToArguments(params); + c.args.push(p); + }); + c.returnType = getParameterType(m.type); + obj.construct = c; + break; + } + } + }); + + return obj; + } + case ts.SyntaxKind.VariableStatement: { + let declaration = (statement as VariableStatement).declarationList.declarations[0]; + let methodName = (declaration.name as ts.Identifier).text; + let type = declaration.type; + let functionObject = new FunctionObject(); + + functionObject.declare = new FunctionDeclaration(); + if (type?.kind == ts.SyntaxKind.FunctionType) { + functionObject.declare.args = (type as ts.FunctionTypeNode).parameters.map(param => paramsNodeToArguments(param)); + functionObject.declare.returnType = getParameterType((type as ts.FunctionTypeNode).type); + functionObject.declare.name = methodName.toString(); + } + + return functionObject; + } + } + + return null; +} diff --git a/bridge/scripts/code_generator/src/idl/declaration.ts b/bridge/scripts/code_generator/src/idl/declaration.ts new file mode 100644 index 0000000000..c0e97ecb82 --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/declaration.ts @@ -0,0 +1,64 @@ +import {ParameterType} from "./analyzer"; + +export enum FunctionArgumentType { + // Basic types + dom_string, + object, + int32, + int64, + double, + boolean, + function, + void, + any, + null, + undefined, + array, +} + +export class FunctionArguments { + name: string; + type: ParameterType[] = []; + required: boolean; +} + +export class ParameterMode { + newObject?: boolean; + dartImpl?: boolean; +} + +export class PropsDeclaration { + type: ParameterType[] = []; + typeMode: ParameterMode; + name: string; + readonly: boolean; +} + +export class IndexedPropertyDeclaration extends PropsDeclaration { + indexKeyType: 'string' | 'number'; +} + +export class FunctionDeclaration extends PropsDeclaration { + args: FunctionArguments[] = []; + returnType: ParameterType[] = []; + returnTypeMode?: ParameterMode; +} + +export enum ClassObjectKind { + interface, + dictionary +} + +export class ClassObject { + name: string; + parent: string; + props: PropsDeclaration[] = []; + indexedProp?: IndexedPropertyDeclaration; + methods: FunctionDeclaration[] = []; + construct?: FunctionDeclaration; + kind: ClassObjectKind = ClassObjectKind.interface +} + +export class FunctionObject { + declare: FunctionDeclaration +} diff --git a/bridge/scripts/code_generator/src/idl/generateHeader.ts b/bridge/scripts/code_generator/src/idl/generateHeader.ts new file mode 100644 index 0000000000..b120cd42b7 --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/generateHeader.ts @@ -0,0 +1,90 @@ +import {ClassObject, ClassObjectKind, FunctionObject} from "./declaration"; +import _ from "lodash"; +import {IDLBlob} from "./IDLBlob"; +import {getClassName} from "./utils"; +import fs from 'fs'; +import path from 'path'; +import {generateIDLTypeConverter, generateTypeValue} from "./generateSource"; +import {GenerateOptions} from "./generator"; + +export enum TemplateKind { + globalFunction, + Dictionary, + Interface, + null +} + +export function getTemplateKind(object: ClassObject | FunctionObject | null): TemplateKind { + if (object instanceof FunctionObject) { + return TemplateKind.globalFunction; + } else if (object instanceof ClassObject) { + if (object.kind === ClassObjectKind.dictionary) { + return TemplateKind.Dictionary; + } + return TemplateKind.Interface; + } + return TemplateKind.null; +} + +function readTemplate(name: string) { + return fs.readFileSync(path.join(__dirname, '../../static/idl_templates/' + name + '.h.tpl'), {encoding: 'utf-8'}); +} + +export function generateCppHeader(blob: IDLBlob, options: GenerateOptions) { + const baseTemplate = fs.readFileSync(path.join(__dirname, '../../static/idl_templates/base.h.tpl'), {encoding: 'utf-8'}); + let headerOptions = { + interface: false, + dictionary: false, + global_function: false, + }; + const contents = blob.objects.map(object => { + const templateKind = getTemplateKind(object); + if (templateKind === TemplateKind.null) return ''; + + switch(templateKind) { + case TemplateKind.Interface: { + if (!headerOptions.interface) { + headerOptions.interface = true; + return _.template(readTemplate('interface'))({ + className: getClassName(blob), + blob: blob, + object, + ...options + }); + } + return ''; + } + case TemplateKind.Dictionary: { + if (!headerOptions.dictionary) { + headerOptions.dictionary = true; + let props = (object as ClassObject).props; + return _.template(readTemplate('dictionary'))({ + className: getClassName(blob), + blob: blob, + object: object, + props, + generateTypeValue: generateTypeValue + }); + } + return ''; + } + case TemplateKind.globalFunction: { + if (!headerOptions.global_function) { + headerOptions.global_function = true; + return _.template(readTemplate('global_function'))({ + className: getClassName(blob), + blob: blob + }); + } + return ''; + } + } + }); + + return _.template(baseTemplate)({ + content: contents.join('\n'), + blob: blob + }).split('\n').filter(str => { + return str.trim().length > 0; + }).join('\n'); +} diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts new file mode 100644 index 0000000000..22edc9e2b2 --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -0,0 +1,457 @@ +import {IDLBlob} from "./IDLBlob"; +import { + ClassObject, + FunctionArguments, + FunctionArgumentType, + FunctionDeclaration, + FunctionObject, + ParameterMode, + PropsDeclaration, +} from "./declaration"; +import {addIndent, getClassName} from "./utils"; +import {ParameterType} from "./analyzer"; +import _ from 'lodash'; +import fs from 'fs'; +import path from 'path'; +import {getTemplateKind, TemplateKind} from "./generateHeader"; +import {GenerateOptions} from "./generator"; + +enum PropType { + hostObject, + Element, + Event +} + +function generateMethodArgumentsCheck(m: FunctionDeclaration) { + if (m.args.length == 0) return ''; + + let requiredArgsCount = 0; + m.args.forEach(m => { + if (m.required) requiredArgsCount++; + }); + + return ` if (argc < ${requiredArgsCount}) { + return JS_ThrowTypeError(ctx, "Failed to execute '${m.name}' : ${requiredArgsCount} argument required, but %d present.", argc); + } +`; +} + +export function generateTypeValue(type: ParameterType[]): string { + switch (type[0]) { + case FunctionArgumentType.int64: { + return 'int64_t'; + } + case FunctionArgumentType.int32: { + return 'int32_t'; + } + case FunctionArgumentType.void: { + return 'void'; + } + case FunctionArgumentType.double: { + return 'double'; + } + case FunctionArgumentType.boolean: { + return 'bool'; + } + case FunctionArgumentType.dom_string: { + return 'AtomicString'; + } + case FunctionArgumentType.any: { + return 'ScriptValue'; + } + } + + if (typeof type[0] == 'string') { + return type[0] + '*'; + } + + return ''; +} + +export function generateIDLTypeConverter(type: ParameterType[]): string { + let haveNull = type.some(t => t === FunctionArgumentType.null); + let returnValue = ''; + + if (type[0] === FunctionArgumentType.array) { + returnValue = `IDLSequence<${generateIDLTypeConverter(type.slice(1))}>`; + } else if (typeof type[0] === 'string') { + returnValue = type[0]; + } else { + switch (type[0]) { + case FunctionArgumentType.int32: + returnValue = `IDLInt32`; + break; + case FunctionArgumentType.int64: + returnValue = 'IDLInt64'; + break; + case FunctionArgumentType.double: + returnValue = `IDLDouble`; + break; + case FunctionArgumentType.function: + returnValue = `IDLCallback`; + break; + case FunctionArgumentType.boolean: + returnValue = `IDLBoolean`; + break; + case FunctionArgumentType.dom_string: + returnValue = `IDLDOMString`; + break; + case FunctionArgumentType.object: + returnValue = `IDLObject`; + break; + default: + case FunctionArgumentType.any: + returnValue = `IDLAny`; + break; + } + } + + if (haveNull) { + returnValue = `IDLNullable<${returnValue}>`; + } + + return returnValue; +} + +function generateNativeValueTypeConverter(type: ParameterType[]): string { + let returnValue = ''; + + switch (type[0]) { + case FunctionArgumentType.int32: + returnValue = `NativeTypeInt64`; + break; + case FunctionArgumentType.int64: + returnValue = 'NativeTypeInt64'; + break; + case FunctionArgumentType.double: + returnValue = `NativeTypeDouble`; + break; + case FunctionArgumentType.boolean: + returnValue = `NativeTypeBool`; + break; + case FunctionArgumentType.dom_string: + returnValue = `NativeTypeString`; + break; + } + + return returnValue; +} + +function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number) { + let type = generateIDLTypeConverter(argument.type); + + let hasArgumentCheck = type.indexOf('Element') >= 0 || type.indexOf('Node') >= 0 || type === 'EventTarget'; + + let body = ''; + if (hasArgumentCheck) { + body = `Converter<${type}>::ArgumentsValue(context, argv[${argsIndex}], ${argsIndex}, exception_state)` + } else { + body = `Converter<${type}>::FromValue(ctx, argv[${argsIndex}], exception_state)`; + } + + return `auto&& args_${argument.name} = ${body}; +if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); +}`; +} + +function generateCallMethodName(name: string) { + if (name === 'constructor') return 'Create'; + return name; +} + +function generateOptionalInitBody(blob: IDLBlob, declare: FunctionDeclaration, argument: FunctionArguments, argsIndex: number, previousArguments: string[], options: GenFunctionBodyOptions) { + let call = ''; + let returnValueAssignment = ''; + if (declare.returnType[0] != FunctionArgumentType.void) { + returnValueAssignment = 'return_value ='; + } + if (options.isInstanceMethod) { + call = `auto* self = toScriptWrappable<${getClassName(blob)}>(this_val); +${returnValueAssignment} self->${generateCallMethodName(declare.name)}(${[...previousArguments, `args_${argument.name}`, 'exception_state'].join(',')});`; + } else { + call = `${returnValueAssignment} ${getClassName(blob)}::${generateCallMethodName(declare.name)}(context, ${[...previousArguments, `args_${argument.name}`].join(',')}, exception_state);`; + } + + + return `auto&& args_${argument.name} = Converter<IDLOptional<${generateIDLTypeConverter(argument.type)}>>::FromValue(ctx, argv[${argsIndex}], exception_state); +if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); +} + +if (argc <= ${argsIndex + 1}) { + ${call} + break; +}`; +} + +function generateFunctionCallBody(blob: IDLBlob, declaration: FunctionDeclaration, options: GenFunctionBodyOptions = { + isConstructor: false, + isInstanceMethod: false +}) { + let minimalRequiredArgc = 0; + declaration.args.forEach(m => { + if (m.required) minimalRequiredArgc++; + }); + + let requiredArguments: string[] = []; + let requiredArgumentsInit: string[] = []; + if (minimalRequiredArgc > 0) { + requiredArgumentsInit = declaration.args.filter((a, i) => a.required).map((a, i) => { + requiredArguments.push(`args_${a.name}`); + return generateRequiredInitBody(a, i); + }); + } + + let optionalArgumentsInit: string[] = []; + let totalArguments: string[] = requiredArguments.slice(); + + for (let i = minimalRequiredArgc; i < declaration.args.length; i++) { + optionalArgumentsInit.push(generateOptionalInitBody(blob, declaration, declaration.args[i], i, totalArguments, options)); + totalArguments.push(`args_${declaration.args[i].name}`); + } + + requiredArguments.push('exception_state'); + + let call = ''; + let returnValueAssignment = ''; + if (declaration.returnType[0] != FunctionArgumentType.void) { + returnValueAssignment = 'return_value ='; + } + if (options.isInstanceMethod) { + call = `auto* self = toScriptWrappable<${getClassName(blob)}>(JS_IsUndefined(this_val) ? context->Global() : this_val); +${returnValueAssignment} self->${generateCallMethodName(declaration.name)}(${minimalRequiredArgc > 0 ? `${requiredArguments.join(',')}` : 'exception_state'});`; + } else { + call = `${returnValueAssignment} ${getClassName(blob)}::${generateCallMethodName(declaration.name)}(context, ${requiredArguments.join(',')});`; + } + + return `${requiredArgumentsInit.join('\n')} +if (argc <= ${minimalRequiredArgc}) { + ${call} + break; +} + +${optionalArgumentsInit.join('\n')} +`; +} + +type OverLoadMethods = { + [name: string]: FunctionDeclaration[]; +}; + +function generateOverLoadSwitchBody(overloadMethods: FunctionDeclaration[]) { + let callBodyList = overloadMethods.map((overload, index) => { + return `if (${overload.args.length} == argc) { + return ${overload.name}_overload_${index}(ctx, this_val, argc, argv); +} + `; + }); + + return ` +${callBodyList.join('\n')} + +return ${overloadMethods[0].name}_overload_${0}(ctx, this_val, argc, argv); +`; +} + +function generateDictionaryInit(blob: IDLBlob, props: PropsDeclaration[]) { + let initExpression = props.map(prop => { + switch (prop.type[0]) { + case FunctionArgumentType.boolean: { + return `${prop.name}_(false)`; + } + } + return '' + }); + + // Remove empty. + initExpression = initExpression.filter(i => !!i); + + if (initExpression.length == 0) return ''; + + return ': ' + initExpression.join(','); +} + +function generateReturnValueInit(blob: IDLBlob, type: ParameterType[], options: GenFunctionBodyOptions = { + isConstructor: false, + isInstanceMethod: false +}) { + if (type[0] == FunctionArgumentType.void) return ''; + + if (options.isConstructor) { + return `${getClassName(blob)}* return_value = nullptr;` + } + if (typeof type[0] === 'string') { + if (type[0] === 'Promise') { + return 'ScriptPromise return_value;'; + } else { + return `${type[0]}* return_value = nullptr;`; + } + } + return `Converter<${generateIDLTypeConverter(type)}>::ImplType return_value;`; +} + +function generateReturnValueResult(blob: IDLBlob, type: ParameterType[], mode?: ParameterMode, options: GenFunctionBodyOptions = { + isConstructor: false, + isInstanceMethod: false +}): string { + if (type[0] == FunctionArgumentType.void) return 'JS_NULL'; + let method = 'ToQuickJS'; + + if (options.isConstructor) { + return `return_value->${method}()`; + } + + if (typeof type[0] === 'string') { + if (type[0] === 'Promise') { + return `return_value.${method}()`; + } else { + return `return_value->${method}()`; + } + } + + return `Converter<${generateIDLTypeConverter(type)}>::ToValue(ctx, std::move(return_value))`; +} + +type GenFunctionBodyOptions = { isConstructor?: boolean, isInstanceMethod?: boolean }; + +function generateIndexedPropertyBody() { + +} + +function generateFunctionBody(blob: IDLBlob, declare: FunctionDeclaration, options: GenFunctionBodyOptions = { + isConstructor: false, + isInstanceMethod: false +}) { + let paramCheck = generateMethodArgumentsCheck(declare); + let callBody = generateFunctionCallBody(blob, declare, options); + let returnValueInit = generateReturnValueInit(blob, declare.returnType, options); + let returnValueResult = generateReturnValueResult(blob, declare.returnType, declare.returnTypeMode, options); + + return `${paramCheck} + + ExceptionState exception_state; + ${returnValueInit} + ExecutingContext* context = ExecutingContext::From(ctx); + MemberMutationScope scope{ExecutingContext::From(ctx)}; + + do { // Dummy loop for use of 'break'. +${addIndent(callBody, 4)} + } while (false); + + if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); + } + return ${returnValueResult}; +`; +} + +function readTemplate(name: string) { + return fs.readFileSync(path.join(__dirname, '../../static/idl_templates/' + name + '.cc.tpl'), {encoding: 'utf-8'}); +} + +export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { + const baseTemplate = fs.readFileSync(path.join(__dirname, '../../static/idl_templates/base.cc.tpl'), {encoding: 'utf-8'}); + + const contents = blob.objects.map(object => { + const templateKind = getTemplateKind(object); + if (templateKind === TemplateKind.null) return ''; + + switch (templateKind) { + case TemplateKind.Interface: { + object = object as ClassObject; + object.props.forEach(prop => { + options.classMethodsInstallList.push(`{"${prop.name}", ${prop.name}AttributeGetCallback, ${prop.readonly ? 'nullptr' : `${prop.name}AttributeSetCallback`}}`) + }); + + let overloadMethods = {}; + let filtedMethods: FunctionDeclaration[] = []; + object.methods.forEach((method, i) => { + if (overloadMethods.hasOwnProperty(method.name)) { + overloadMethods[method.name].push(method) + } else { + overloadMethods[method.name] = [method]; + filtedMethods.push(method); + options.classPropsInstallList.push(`{"${method.name}", ${method.name}, ${method.args.length}}`) + } + }); + + if (object.construct) { + options.constructorInstallList.push(`{"${getClassName(blob)}", nullptr, nullptr, constructor}`) + } + + let wrapperTypeRegisterList = [ + `JS_CLASS_${_.snakeCase(getClassName(blob)).toUpperCase()}`, // ClassId + `"${getClassName(blob)}"`, // ClassName + object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr', // parentClassWrapper + object.construct ? `QJS${getClassName(blob)}::ConstructorCallback` : 'nullptr', // ConstructorCallback + ]; + + // Generate indexed property callback. + if (object.indexedProp) { + if (object.indexedProp.indexKeyType == 'number') { + wrapperTypeRegisterList.push(`IndexedPropertyGetterCallback`); + if (!object.indexedProp.readonly) { + wrapperTypeRegisterList.push(`IndexedPropertySetterCallback`); + } + } else { + wrapperTypeRegisterList.push('nullptr'); + wrapperTypeRegisterList.push('nullptr'); + + wrapperTypeRegisterList.push(`StringPropertyGetterCallback`); + if (!object.indexedProp.readonly) { + wrapperTypeRegisterList.push(`StringPropertySetterCallback`); + } + } + } + + options.wrapperTypeInfoInit = ` +const WrapperTypeInfo QJS${getClassName(blob)}::wrapper_type_info_ {${wrapperTypeRegisterList.join(', ')}}; +const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClassName(blob)}::wrapper_type_info_;`; + return _.template(readTemplate('interface'))({ + className: getClassName(blob), + blob: blob, + object: object, + generateFunctionBody, + generateTypeValue, + generateOverLoadSwitchBody, + overloadMethods, + filtedMethods, + generateIDLTypeConverter, + generateNativeValueTypeConverter + }); + } + case TemplateKind.Dictionary: { + let props = (object as ClassObject).props; + return _.template(readTemplate('dictionary'))({ + className: getClassName(blob), + blob: blob, + props: props, + object: object, + generateIDLTypeConverter, + generateDictionaryInit + }); + } + case TemplateKind.globalFunction: { + object = object as FunctionObject; + options.globalFunctionInstallList.push(` {"${object.declare.name}", ${object.declare.name}, ${object.declare.args.length}}`); + return _.template(readTemplate('global_function'))({ + className: getClassName(blob), + blob: blob, + object: object, + generateFunctionBody + }); + } + } + return ''; + }); + + return _.template(baseTemplate)({ + content: contents.join('\n'), + className: getClassName(blob), + blob: blob, + ...options + }).split('\n').filter(str => { + return str.trim().length > 0; + }).join('\n'); +} diff --git a/bridge/scripts/code_generator/src/idl/generator.ts b/bridge/scripts/code_generator/src/idl/generator.ts new file mode 100644 index 0000000000..576f63c91f --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/generator.ts @@ -0,0 +1,41 @@ +import {IDLBlob} from './IDLBlob'; +import {generateCppHeader} from "./generateHeader"; +import {generateCppSource} from "./generateSource"; + +function generateSupportedOptions(): GenerateOptions { + let globalFunctionInstallList: string[] = []; + let classMethodsInstallList: string[] = []; + let constructorInstallList: string[] = []; + let classPropsInstallList: string[] = []; + let indexedProperty: string = ''; + let wrapperTypeInfoInit = ''; + + return { + globalFunctionInstallList, + classPropsInstallList, + classMethodsInstallList, + constructorInstallList, + indexedProperty, + wrapperTypeInfoInit + }; +} + +export type GenerateOptions = { + globalFunctionInstallList: string[]; + classMethodsInstallList: string[]; + constructorInstallList: string[]; + classPropsInstallList: string[]; + wrapperTypeInfoInit: string; + indexedProperty: string; +}; + +export function generatorSource(blob: IDLBlob) { + let options = generateSupportedOptions(); + + let source = generateCppSource(blob, options); + let header = generateCppHeader(blob, options); + return { + header, + source + }; +} diff --git a/bridge/scripts/code_generator/src/idl/utils.ts b/bridge/scripts/code_generator/src/idl/utils.ts new file mode 100644 index 0000000000..4d7fd6f040 --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/utils.ts @@ -0,0 +1,31 @@ +import {IDLBlob} from './IDLBlob'; +import {camelCase} from 'lodash'; + +export function addIndent(str: String, space: number) { + let lines = str.split('\n'); + lines = lines.map(l => { + for (let i = 0; i < space; i ++) { + l = ' ' + l; + } + return l; + }); + return lines.join('\n'); +} + +export function getClassName(blob: IDLBlob) { + let raw = camelCase(blob.filename[4].toUpperCase() + blob.filename.slice(5)); + + if (raw.slice(0, 4) == 'html') { + return 'HTML' + raw.slice(4); + } + + if (raw.slice(0, 3) == 'css') { + return 'CSS' + raw.slice(3); + } + + return `${raw[0].toUpperCase() + raw.slice(1)}`; +} + +export function getMethodName(name: string) { + return name[0].toUpperCase() + name.slice(1); +} diff --git a/bridge/scripts/code_generator/src/blob.ts b/bridge/scripts/code_generator/src/json/JSONBlob.ts similarity index 59% rename from bridge/scripts/code_generator/src/blob.ts rename to bridge/scripts/code_generator/src/json/JSONBlob.ts index 77237f10b7..5a24233cfa 100644 --- a/bridge/scripts/code_generator/src/blob.ts +++ b/bridge/scripts/code_generator/src/json/JSONBlob.ts @@ -1,17 +1,19 @@ -import fs from 'fs'; -import {ClassObject} from "./declaration"; +import {ClassObject, FunctionObject} from "../idl/declaration"; +import fs from "fs"; +import JSON5 from 'json5'; -export class Blob { +export class JSONBlob { raw: string; dist: string; source: string; filename: string; - objects: ClassObject[]; + json: any; constructor(source: string, dist: string, filename: string) { this.source = source; this.raw = fs.readFileSync(source, {encoding: 'utf-8'}); this.dist = dist; this.filename = filename; + this.json = JSON5.parse(this.raw); } } diff --git a/bridge/scripts/code_generator/src/json/JSONTemplate.ts b/bridge/scripts/code_generator/src/json/JSONTemplate.ts new file mode 100644 index 0000000000..a3b82df837 --- /dev/null +++ b/bridge/scripts/code_generator/src/json/JSONTemplate.ts @@ -0,0 +1,17 @@ +import fs from "fs"; + +enum TemplateType { + header, + body +} + +export class JSONTemplate { + public raw: string; + public filename: string; + public type: TemplateType; + constructor(source: string, filename: string) { + this.filename = filename; + this.type = filename.indexOf('.h') >= 0 ? TemplateType.header : TemplateType.body; + this.raw = fs.readFileSync(source, {encoding: 'utf-8'}) + } +} diff --git a/bridge/scripts/code_generator/src/json/generator.ts b/bridge/scripts/code_generator/src/json/generator.ts new file mode 100644 index 0000000000..5588fe66c0 --- /dev/null +++ b/bridge/scripts/code_generator/src/json/generator.ts @@ -0,0 +1,36 @@ +import {JSONBlob} from './JSONBlob'; +import {JSONTemplate} from './JSONTemplate'; +import _ from 'lodash'; + +function generateHeader(blob: JSONBlob, template: JSONTemplate): string { + let compiled = _.template(template.raw); + return compiled({ + _: _, + name: blob.filename, + template_path: blob.source, + data: blob.json.data + }).split('\n').filter(str => { + return str.trim().length > 0; + }).join('\n'); +} + +function generateBody(blob: JSONBlob, template: JSONTemplate): string { + let compiled = _.template(template.raw); + return compiled({ + template_path: blob.source, + name: blob.filename, + data: blob.json.data + }).split('\n').filter(str => { + return str.trim().length > 0; + }).join('\n'); +} + +export function generateJSONTemplate(blob: JSONBlob, headerTemplate: JSONTemplate, bodyTemplate?: JSONTemplate) { + let header = generateHeader(blob, headerTemplate); + let body = bodyTemplate ? generateBody(blob, bodyTemplate) : ''; + + return { + header: header, + source: body, + }; +} diff --git a/bridge/scripts/code_generator/src/utils.ts b/bridge/scripts/code_generator/src/utils.ts deleted file mode 100644 index 6e5524b06f..0000000000 --- a/bridge/scripts/code_generator/src/utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function addIndent(str: String, space: number) { - let lines = str.split('\n'); - lines = lines.map(l => { - for (let i = 0; i < space; i ++) { - l = ' ' + l; - } - return l; - }); - return lines.join('\n'); -} diff --git a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl new file mode 100644 index 0000000000..608dfed39a --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "<%= blob.filename %>.h" +#include "foundation/native_value_converter.h" +#include "binding_call_methods.h" +#include "bindings/qjs/member_installer.h" +#include "bindings/qjs/qjs_function.h" +#include "bindings/qjs/converter_impl.h" +#include "bindings/qjs/script_wrappable.h" +#include "bindings/qjs/script_promise.h" +#include "bindings/qjs/cppgc/mutation_scope.h" +#include "core/executing_context.h" + +namespace kraken { + +<% if (wrapperTypeInfoInit) { %> +<%= wrapperTypeInfoInit %> +<% } %> +<%= content %> + +<% if (globalFunctionInstallList.length > 0 || classPropsInstallList.length > 0 || classMethodsInstallList.length > 0 || constructorInstallList.length > 0) { %> +void QJS<%= className %>::Install(ExecutingContext* context) { + <% if (globalFunctionInstallList.length > 0) { %> InstallGlobalFunctions(context); <% } %> + <% if(classPropsInstallList.length > 0) { %> InstallPrototypeProperties(context); <% } %> + <% if(classMethodsInstallList.length > 0) { %> InstallPrototypeMethods(context); <% } %> + <% if(constructorInstallList.length > 0) { %> InstallConstructor(context); <% } %> +} + +<% } %> + +<% if(globalFunctionInstallList.length > 0) { %> +void QJS<%= className %>::InstallGlobalFunctions(ExecutingContext* context) { + std::initializer_list<MemberInstaller::FunctionConfig> functionConfig { + <%= globalFunctionInstallList.join(',\n') %> + }; + MemberInstaller::InstallFunctions(context, context->Global(), functionConfig); +} +<% } %> + +<% if(classPropsInstallList.length > 0) { %> +void QJS<%= className %>::InstallPrototypeProperties(ExecutingContext* context) { + const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); + JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); + std::initializer_list<MemberInstaller::FunctionConfig> functionConfig { + <%= classPropsInstallList.join(',\n') %> + }; + MemberInstaller::InstallFunctions(context, prototype, functionConfig); +} +<% } %> + +<% if(classMethodsInstallList.length > 0) { %> +void QJS<%= className %>::InstallPrototypeMethods(ExecutingContext* context) { + const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); + JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); + + std::initializer_list<MemberInstaller::AttributeConfig> attributesConfig { + <%= classMethodsInstallList.join(',\n') %> + }; + + MemberInstaller::InstallAttributes(context, prototype, attributesConfig); +} +<% } %> + +<% if (constructorInstallList.length > 0) { %> +void QJS<%= className %>::InstallConstructor(ExecutingContext* context) { + const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); + JSValue constructor = context->contextData()->constructorForType(wrapperTypeInfo); + + std::initializer_list<MemberInstaller::AttributeConfig> attributeConfig { + <%= constructorInstallList.join(',\n') %> + }; + MemberInstaller::InstallAttributes(context, context->Global(), attributeConfig); +} +<% } %> + +} diff --git a/bridge/scripts/code_generator/static/idl_templates/base.h.tpl b/bridge/scripts/code_generator/static/idl_templates/base.h.tpl new file mode 100644 index 0000000000..a865cd3923 --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/base.h.tpl @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_<%= blob.filename.toUpperCase() %>_H +#define KRAKENBRIDGE_<%= blob.filename.toUpperCase() %>_H + +#include <quickjs/quickjs.h> +#include "bindings/qjs/wrapper_type_info.h" +#include "bindings/qjs/generated_code_helper.h" + +<%= content %> + +#endif //KRAKENBRIDGE_<%= blob.filename.toUpperCase() %>T_H diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl new file mode 100644 index 0000000000..72968a5df2 --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl @@ -0,0 +1,48 @@ +std::shared_ptr<<%= className %>> <%= className %>::Create() { + return std::make_shared<<%= className %>>(); +} +std::shared_ptr<<%= className %>> <%= className %>::Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + return std::make_shared<<%= className %>>(ctx, value, exception_state); +} + +<%= className %>::<%= className %>() <%= generateDictionaryInit(blob, props) %> {} +<%= className %>::<%= className %>(JSContext* ctx, JSValue value, ExceptionState& exception_state): <%= className %>() { + FillMembersWithQJSObject(ctx, value, exception_state); +} + +bool <%= className %>::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const { + <% if (object.parent) { %> + <%= object.parent %>::FillQJSObjectWithMembers(ctx, qjs_dictionary); + <% } %> + + if (!JS_IsObject(qjs_dictionary)) { + return false; + } + + <% _.forEach(props, function(prop, index) { %> + JS_SetPropertyStr(ctx, qjs_dictionary, "<%= prop.name %>_", Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, <%= prop.name %>_)); + <% }); %> + + return true; +} + +void <%= className %>::FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + <% if (object.parent) { %> + <%= object.parent %>::FillMembersWithQJSObject(ctx, value, exception_state); + <% } %> + + if (!JS_IsObject(value)) { + return; + } + + <% _.forEach(props, function(prop, index) { %> + { + JSValue v = JS_GetPropertyStr(ctx, value, "<%= prop.name %>"); + <%= prop.name %>_ = Converter<<%= generateIDLTypeConverter(prop.type) %>>::FromValue(ctx, v, exception_state); + JS_FreeValue(ctx, v); + } + + <% }); %> + + +} diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl new file mode 100644 index 0000000000..cecbe09048 --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl @@ -0,0 +1,31 @@ + +<% if (object.parent) { %> +#include "qjs_<%= _.snakeCase(object.parent) %>.h" +<% } %> + +namespace kraken { + +class ExecutingContext; +class ExceptionState; + +class <%= className %> : public <%= object.parent ? object.parent : 'DictionaryBase' %> { + public: + using ImplType = std::shared_ptr<<%= className %>>; + static std::shared_ptr<<%= className %>> Create(); + static std::shared_ptr<<%= className %>> Create(JSContext* ctx, JSValue value, ExceptionState& exception_state); + explicit <%= className %>(); + explicit <%= className %>(JSContext* ctx, JSValue value, ExceptionState& exception_state); + + <% _.forEach(props, (function(prop, index) { %> + <%= generateTypeValue(prop.type) %> <%= prop.name %>() const { return <%= prop.name %>_; } + void set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(<%= generateTypeValue(prop.type) %> value) { <%= prop.name %>_ = value; } + <% })); %> + bool FillQJSObjectWithMembers(JSContext *ctx, JSValue qjs_dictionary) const override; + void FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state); +private: + <% _.forEach(props, (function(prop, index) { %> + <%= generateTypeValue(prop.type) %> <%= prop.name %>_; + <% })); %> +}; + +} diff --git a/bridge/scripts/code_generator/static/idl_templates/global_function.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/global_function.cc.tpl new file mode 100644 index 0000000000..3abca6a798 --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/global_function.cc.tpl @@ -0,0 +1,3 @@ +static JSValue ${object.declare.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + <%= generateFunctionBody(blob, object.declare) %> +} diff --git a/bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl b/bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl new file mode 100644 index 0000000000..059bb60cda --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl @@ -0,0 +1,14 @@ +#include "core/<%= blob.implement %>.h" + +namespace kraken { + +class ExecutingContext; + +class QJS<%= className %> final { + public: + static void Install(ExecutingContext* context); + private: + static void InstallGlobalFunctions(ExecutingContext* context); +}; + +} diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl new file mode 100644 index 0000000000..6d7e412f6c --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -0,0 +1,130 @@ +<% if (object.construct) { %> +JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags) { + <%= generateFunctionBody(blob, object.construct, {isConstructor: true}) %> +} +<% } %> + +<% if (object.indexedProp) { %> + <% if (object.indexedProp.indexKeyType == 'number') { %> + JSValue QJS<%= className %>::IndexedPropertyGetterCallback(JSContext* ctx, JSValue obj, uint32_t index) { + auto* self = toScriptWrappable<<%= className %>>(obj); + if (index >= self->length()) { + return JS_UNDEFINED; + } + ExceptionState exception_state; + MemberMutationScope scope{ExecutingContext::From(ctx)}; + <%= generateTypeValue(object.indexedProp.type) %> result = self->item(index, exception_state); + if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); + } + + return Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::ToValue(ctx, result); + }; + <% } else { %> + JSValue QJS<%= className %>::StringPropertyGetterCallback(JSContext* ctx, JSValue obj, JSAtom key) { + auto* self = toScriptWrappable<<%= className %>>(obj); + ExceptionState exception_state; + MemberMutationScope scope{ExecutingContext::From(ctx)}; + ${generateTypeValue(object.indexedProp.type)} result = self->item(AtomicString(ctx, key), exception_state); + if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); + } + return Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::ToValue(ctx, result); + }; + <% } %> + <% if (!object.indexedProp.readonly) { %> + <% if (object.indexedProp.indexKeyType == 'number') { %> + bool QJS<%= className %>::IndexedPropertySetterCallback(JSContext* ctx, JSValueConst obj, uint32_t index, JSValueConst value) { + auto* self = toScriptWrappable<<%= className %>>(obj); + ExceptionState exception_state; + MemberMutationScope scope{ExecutingContext::From(ctx)}; + auto&& v = Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::FromValue(ctx, value, exception_state); + if (UNLIKELY(exception_state.HasException())) { + return false; + } + bool success = self->SetItem(index, v, exception_state); + if (UNLIKELY(exception_state.HasException())) { + return false; + } + return success; + }; + <% } else { %> + bool QJS<%= className %>::StringPropertySetterCallback(JSContext* ctx, JSValueConst obj, JSAtom key, JSValueConst value) { + auto* self = toScriptWrappable<<%= className %>>(obj); + ExceptionState exception_state; + MemberMutationScope scope{ExecutingContext::From(ctx)}; + auto&& v = Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::FromValue(ctx, value, exception_state); + if (UNLIKELY(exception_state.HasException())) { + return false; + } + bool success = self->SetItem(AtomicString(ctx, key), v, exception_state); + if (UNLIKELY(exception_state.HasException())) { + return false; + } + return success; + }; + <% } %> + <% } %> + <% } %> + + +<% _.forEach(filtedMethods, function(method, index) { %> + + <% if (overloadMethods[method.name] && overloadMethods[method.name].length > 1) { %> + <% _.forEach(overloadMethods[method.name], function(overloadMethod, index) { %> +static JSValue <%= overloadMethod.name %>_overload_<%= index %>(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + <%= generateFunctionBody(blob, overloadMethod, {isInstanceMethod: true}) %> + } + <% }); %> + static JSValue <%= method.name %>(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + <%= generateOverLoadSwitchBody(overloadMethods[method.name]) %> + } + <% } else { %> + + static JSValue <%= method.name %>(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + <%= generateFunctionBody(blob, method, {isInstanceMethod: true}) %> + } + <% } %> + +<% }) %> + +<% _.forEach(object.props, function(prop, index) { %> +static JSValue <%= prop.name %>AttributeGetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* <%= blob.filename %> = toScriptWrappable<<%= className %>>(this_val); + assert(<%= blob.filename %> != nullptr); + MemberMutationScope scope{ExecutingContext::From(ctx)}; + + <% if (prop.typeMode && prop.typeMode.dartImpl) { %> + ExceptionState exception_state; + typename <%= generateNativeValueTypeConverter(prop.type) %>::ImplType v = NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::FromNativeValue(<%= blob.filename %>->GetBindingProperty(binding_call_methods::k<%= prop.name %>, exception_state)); + if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); + } + return Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, v); + <% } else { %> + return Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, <%= blob.filename %>-><%= prop.name %>()); + <% } %> +} +<% if (!prop.readonly) { %> +static JSValue <%= prop.name %>AttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* <%= blob.filename %> = toScriptWrappable<<%= className %>>(this_val); + ExceptionState exception_state; + auto&& v = Converter<<%= generateIDLTypeConverter(prop.type) %>>::FromValue(ctx, argv[0], exception_state); + if (exception_state.HasException()) { + return exception_state.ToQuickJS(); + } + MemberMutationScope scope{ExecutingContext::From(ctx)}; + + <% if (prop.typeMode && prop.typeMode.dartImpl) { %> + <%= blob.filename %>->SetBindingProperty(binding_call_methods::k<%= prop.name %>, NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::ToNativeValue(v),exception_state); + <% } else {%> + <%= blob.filename %>->set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(v, exception_state); + <% } %> + if (exception_state.HasException()) { + return exception_state.ToQuickJS(); + } + + return JS_DupValue(ctx, argv[0]); +} +<% } %> +<% }); %> diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl new file mode 100644 index 0000000000..63e4b3246d --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl @@ -0,0 +1,39 @@ +#include "core/<%= blob.implement %>.h" + +namespace kraken { + +class ExecutingContext; + +class QJS<%= className %> : public QJSInterfaceBridge<QJS<%= className %>, <%= className%>> { + public: + static void Install(ExecutingContext* context); + static WrapperTypeInfo* GetWrapperTypeInfo() { + return const_cast<WrapperTypeInfo*>(&wrapper_type_info_); + } + <% if (object.construct) { %> static JSValue ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags); <% } %> + static const WrapperTypeInfo wrapper_type_info_; + private: + <% if (globalFunctionInstallList.length > 0) { %> static void InstallGlobalFunctions(ExecutingContext* context); <% } %> + <% if (classMethodsInstallList.length > 0) { %> static void InstallPrototypeMethods(ExecutingContext* context); <% } %> + <% if (classPropsInstallList.length > 0) { %> static void InstallPrototypeProperties(ExecutingContext* context); <% } %> + <% if (object.construct) { %> static void InstallConstructor(ExecutingContext* context); <% } %> + + <% if (object.indexedProp) { %> + <% if (object.indexedProp.indexKeyType == 'number') { %> + static JSValue IndexedPropertyGetterCallback(JSContext* ctx, JSValue obj, uint32_t index); + <% } else { %> + static JSValue StringPropertyGetterCallback(JSContext* ctx, JSValue obj, JSAtom key); + <% } %> + <% if (!object.indexedProp.readonly) { %> + + <% if (object.indexedProp.indexKeyType == 'number') { %> + static bool IndexedPropertySetterCallback(JSContext* ctx, JSValueConst obj, uint32_t index, JSValueConst value); + <% } else { %> + static bool StringPropertySetterCallback(JSContext* ctx, JSValueConst obj, JSAtom key, JSValueConst value); + <% } %> + <% } %> + <% } %> +}; + + +} diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl new file mode 100644 index 0000000000..7705df96da --- /dev/null +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + + // Generated from template: + // code_generator/src/json/templates/element_factory.cc.tmp + // and input files: + // <%= template_path %> + +#include "html_element_factory.h" +#include <unordered_map> +#include "html_names.h" +#include "bindings/qjs/cppgc/garbage_collected.h" + +<% _.forEach(data, (item, index) => { %> + <% if (_.isString(item)) { %> +#include "core/html/html_<%= item %>_element.h" + <% } else if (_.isObject(item)) { %> + <% if (item.interfaceHeaderDir) { %> +#include "<%= item.interfaceHeaderDir %>/html_<%= item.filename ? item.filename : item.name %>_element.h" + <% } else if (item.interfaceName != 'HTMLElement'){ %> +#include "core/html/<%= item.filename ? item.filename : `html_${item.name}_element` %>.h" + <% } %> + <% } %> +<% }); %> + + +namespace kraken { + +using HTMLConstructorFunction = HTMLElement* (*)(Document&); + +using HTMLFunctionMap = std::unordered_map<AtomicString, HTMLConstructorFunction, AtomicString::KeyHasher>; + +static HTMLFunctionMap* g_html_constructors = nullptr; + +struct CreateHTMLFunctionMapData { + const AtomicString& tag; + HTMLConstructorFunction func; +}; + + +<% _.forEach(data, (item, index) => { %> + <% if (_.isString(item)) { %> + +static HTMLElement* HTML<%= _.upperFirst(item) %>Constructor(Document& document) { + return MakeGarbageCollected<HTML<%= _.upperFirst(item) %>Element>(document); +} + <% } else if (_.isObject(item)) { %> + <% if (item.interfaceName) { %> +static HTMLElement* HTML<%= _.upperFirst(item.name) %>Constructor(Document& document) { + return MakeGarbageCollected<<%= item.interfaceName %>>(document); +} + <% } else { %> +static HTMLElement* HTML<%= _.upperFirst(item.name) %>Constructor(Document& document) { + return MakeGarbageCollected<HTML<%= _.upperFirst(item.name) %>Element>(document); +} + <% } %> + <% } %> +<% }); %> + +static void CreateHTMLFunctionMap() { + assert(!g_html_constructors); + g_html_constructors = new HTMLFunctionMap(); + // Empty array initializer lists are illegal [dcl.init.aggr] and will not + // compile in MSVC. If tags list is empty, add check to skip this. + + static const CreateHTMLFunctionMapData data[] = { + +<% _.forEach(data, (item, index) => { %> + <% if (_.isString(item)) { %> + {html_names::k<%= item %>, HTML<%= _.upperFirst(item) %>Constructor}, + <% } else if (_.isObject(item)) { %> + <% if (item.interfaceName) { %> + {html_names::k<%= item.name %>, HTML<%= _.upperFirst(item.name) %>Constructor}, + <% } else { %> + {html_names::k<%= item.name %>, HTML<%= _.upperFirst(item.name) %>Constructor}, + <% } %> + <% } %> +<% }); %> + + }; + + for (size_t i = 0; i < std::size(data); i++) + g_html_constructors->insert(std::make_pair(data[i].tag, data[i].func)); +} + +HTMLElement* HTMLElementFactory::Create(const AtomicString& name, Document& document) { + if (!g_html_constructors) + CreateHTMLFunctionMap(); + auto it = g_html_constructors->find(name); + if (it == g_html_constructors->end()) + return nullptr; + HTMLConstructorFunction function = it->second; + return function(document); +} + +void HTMLElementFactory::Dispose() { + delete g_html_constructors; + g_html_constructors = nullptr; +} + +} // namespace kraken diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl new file mode 100644 index 0000000000..6aaf25e7eb --- /dev/null +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ +#define KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ + +#include "bindings/qjs/atomic_string.h" + +namespace kraken { + +class Document; +class HTMLElement; + +class HTMLElementFactory { + public: + // If |local_name| is unknown, nullptr is returned. + static HTMLElement* Create(const AtomicString& local_name, Document&); + static void Dispose(); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ diff --git a/bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl b/bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl new file mode 100644 index 0000000000..47c98314b7 --- /dev/null +++ b/bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl @@ -0,0 +1,63 @@ + // Generated from template: + // code_generator/src/json/templates/element_type_helper.h.tpl + // and input files: + // <%= template_path %> + +#ifndef KRAKENBRIDGE_CORE_HTML_TYPE_HELPER_H_ +#define KRAKENBRIDGE_CORE_HTML_TYPE_HELPER_H_ + + +#include "core/dom/element.h" +#include "html_names.h" + +<% _.forEach(data, (item, index) => { %> + <% if (_.isString(item)) { %> +#include "core/html/html_<%= item %>_element.h" + <% } else if (_.isObject(item)) { %> + <% if (item.interfaceHeaderDir) { %> +#include "<%= item.interfaceHeaderDir %>/html_<%= item.filename ? item.filename : item.name %>_element.h" + <% } else if (item.interfaceName != 'HTMLElement'){ %> +#include "core/html/<%= item.filename ? item.filename : `html_${item.name}_element` %>.h" + <% } %> + <% } %> +<% }); %> + + +namespace kraken { + + +<% function generateTypeHelperTemplate(name) { + return ` +class HTML${_.upperFirst(name)}Element; +template <> +inline bool IsElementOfType<const HTML${_.upperFirst(name)}Element>(const Node& node) { + return IsA<HTML${_.upperFirst(name)}Element>(node); +} +template <> +inline bool IsElementOfType<const HTML${_.upperFirst(name)}Element>(const HTMLElement& element) { + return IsA<HTML${_.upperFirst(name)}Element>(element); +} +template <> +struct DowncastTraits<HTML${_.upperFirst(name)}Element> { + static bool AllowFrom(const Element& element) { + return element.HasTagName(html_names::k${name}); + } + static bool AllowFrom(const Node& node) { + return node.IsHTMLElement() && IsA<HTML${_.upperFirst(name)}Element>(To<HTMLElement>(node)); + } +}; +`; +} %> + +<% _.forEach(data, (item, index) => { %> + <% if (_.isString(item)) { %> + <%= generateTypeHelperTemplate(item) %> + <% } else if (_.isObject(item)) { %> + <%= generateTypeHelperTemplate(item.name) %> + <% } %> +<% }) %> + +} + + +#endif diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl new file mode 100644 index 0000000000..5a67cae366 --- /dev/null +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -0,0 +1,54 @@ +// Generated from template: +// code_generator/src/json/templates/make_names.h.tmpl +// and input files: +// <%= template_path %> + +#include "<%= name %>.h" + +namespace kraken { +namespace <%= name %> { + +void* names_storage[kNamesCount * ((sizeof(AtomicString) + sizeof(void *) - 1) / sizeof(void *))]; + +<% _.forEach(data, function(name, index) { %> + <% if (_.isArray(name)) { %> +const AtomicString& k<%= name[0] %> = reinterpret_cast<AtomicString*>(&names_storage)[<%= index %>]; + <% } else if (_.isObject(name)) { %> +const AtomicString& k<%= name.name %> = reinterpret_cast<AtomicString*>(&names_storage)[<%= index %>]; + <% } else { %> +const AtomicString& k<%= name %> = reinterpret_cast<AtomicString*>(&names_storage)[<%= index %>];<% } %> +<% }) %> + +void Init(JSContext* ctx) { + struct NameEntry { + const char* str; + }; + + static const NameEntry kNames[] = { + <% _.forEach(data, function(name) { %> + <% if (Array.isArray(name)) { %> + { "<%= name[1] %>" }, + <% } else if(_.isObject(name)) { %> + { "<%= name.name %>" }, + <% } else { %> + { "<%= name %>" }, + <% } %> + <% }); %> + }; + + for(size_t i = 0; i < std::size(kNames); i ++) { + void* address = reinterpret_cast<AtomicString*>(&names_storage) + i; + new (address) AtomicString(ctx, kNames[i].str); + } +}; + +void Dispose(){ + for(size_t i = 0; i < kNamesCount; i ++) { + AtomicString* atomic_string = reinterpret_cast<AtomicString*>(&names_storage) + i; + atomic_string->~AtomicString(); + } +}; + + +} +} // kraken diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl new file mode 100644 index 0000000000..44fb2dbb49 --- /dev/null +++ b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl @@ -0,0 +1,34 @@ +// Generated from template: +// code_generator/src/json/templates/make_names.h.tmpl +// and input files: +// <%= template_path %> + + +#ifndef <%= _.snakeCase(name).toUpperCase() %>_H_ +#define <%= _.snakeCase(name).toUpperCase() %>_H_ + +#include "bindings/qjs/atomic_string.h" + +namespace kraken { +namespace <%= name %> { + +<% _.forEach(data, function(name, index) { %> + <% if (_.isArray(name)) { %> + extern const AtomicString& k<%= name[0] %>; + <% } else if (_.isObject(name)) { %> + extern const AtomicString& k<%= name.name %>; + <% } else { %> + extern const AtomicString& k<%= name %>; + <% } %> +<% }) %> + +constexpr unsigned kNamesCount = <%= data.length %>; + +void Init(JSContext* ctx); +void Dispose(); + +} + +} // kraken + +#endif // #define <%= _.snakeCase(name).toUpperCase() %> diff --git a/bridge/scripts/code_generator/tsconfig.json b/bridge/scripts/code_generator/tsconfig.json index 781a19a406..9ebac78b8d 100644 --- a/bridge/scripts/code_generator/tsconfig.json +++ b/bridge/scripts/code_generator/tsconfig.json @@ -1,11 +1,10 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2020", + "target": "es6", "lib": [ "es6", - "es7", - "dom" + "es7" ], "allowJs": false, "moduleResolution": "node", diff --git a/bridge/test/benchmark/create_element.cc b/bridge/test/benchmark/create_element.cc index ecf29b6406..20806e6f89 100644 --- a/bridge/test/benchmark/create_element.cc +++ b/bridge/test/benchmark/create_element.cc @@ -9,20 +9,20 @@ auto bridge = TEST_init(); static void CreateRawJavaScriptObjects(benchmark::State& state) { - auto& context = bridge->getContext(); + auto& context = bridge->GetExecutingContext(); std::string code = "var a = {}"; // Perform setup here for (auto _ : state) { - context->evaluateJavaScript(code.c_str(), code.size(), "internal://", 0); + context->EvaluateJavaScript(code.c_str(), code.size(), "internal://", 0); } } static void CreateDivElement(benchmark::State& state) { - auto& context = bridge->getContext(); - std::string code = "var a = document.createElement('div');"; + auto& context = bridge->GetExecutingContext(); + std::string code = "var a = document.kCreateElement('div');"; // Perform setup here for (auto _ : state) { - context->evaluateJavaScript(code.c_str(), code.size(), "internal://", 0); + context->EvaluateJavaScript(code.c_str(), code.size(), "internal://", 0); } } diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc new file mode 100644 index 0000000000..65b78390c3 --- /dev/null +++ b/bridge/test/kraken_test_context.cc @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "kraken_test_context.h" +#include "bindings/qjs/member_installer.h" +#include "core/dom/document.h" +#include "core/fileapi/blob.h" +#include "core/html/parser/html_parser.h" +#include "qjs_blob.h" +#include "testframework.h" + +namespace kraken { + +static JSValue executeTest(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + JSValue& callback = argv[0]; + auto context = static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx)); + if (!JS_IsObject(callback)) { + return JS_ThrowTypeError(ctx, "Failed to execute 'executeTest': parameter 1 (callback) is not an function."); + } + + if (!JS_IsFunction(ctx, callback)) { + return JS_ThrowTypeError(ctx, "Failed to execute 'executeTest': parameter 1 (callback) is not an function."); + } + auto bridge = static_cast<KrakenPage*>(context->owner()); + auto bridgeTest = static_cast<KrakenTestContext*>(bridge->owner); + bridgeTest->execute_test_callback_ = QJSFunction::Create(ctx, callback); + return JS_NULL; +} + +static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + JSValue& blobValue = argv[0]; + JSValue& screenShotValue = argv[1]; + JSValue& callbackValue = argv[2]; + auto* context = static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx)); + + if (!QJSBlob::HasInstance(context, blobValue)) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); + } + auto* blob = toScriptWrappable<Blob>(blobValue); + + if (blob == nullptr) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); + } + + if (!JS_IsString(screenShotValue)) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 2 (match) must be an string."); + } + + if (!JS_IsObject(callbackValue)) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); + } + + if (!JS_IsFunction(ctx, callbackValue)) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); + } + + if (context->dartMethodPtr()->matchImageSnapshot == nullptr) { + return JS_ThrowTypeError( + ctx, + "Failed to execute '__kraken_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); + } + + std::unique_ptr<NativeString> screenShotNativeString = kraken::jsValueToNativeString(ctx, screenShotValue); + auto* callbackContext = new ImageSnapShotContext{JS_DupValue(ctx, callbackValue), context}; + + auto fn = [](void* ptr, int32_t contextId, int8_t result, const char* errmsg) { + auto* callbackContext = static_cast<ImageSnapShotContext*>(ptr); + JSContext* ctx = callbackContext->context->ctx(); + + if (errmsg == nullptr) { + JSValue arguments[] = {JS_NewBool(ctx, result != 0), JS_NULL}; + JSValue returnValue = JS_Call(ctx, callbackContext->callback, callbackContext->context->Global(), 1, arguments); + callbackContext->context->HandleException(&returnValue); + } else { + JSValue errmsgValue = JS_NewString(ctx, errmsg); + JSValue arguments[] = {JS_NewBool(ctx, false), errmsgValue}; + JSValue returnValue = JS_Call(ctx, callbackContext->callback, callbackContext->context->Global(), 2, arguments); + callbackContext->context->HandleException(&returnValue); + JS_FreeValue(ctx, errmsgValue); + } + + callbackContext->context->DrainPendingPromiseJobs(); + JS_FreeValue(callbackContext->context->ctx(), callbackContext->callback); + }; + + context->dartMethodPtr()->matchImageSnapshot(callbackContext, context->contextId(), blob->bytes(), blob->size(), + screenShotNativeString.get(), fn); + return JS_NULL; +} + +static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* context = ExecutingContext::From(ctx); +#if FLUTTER_BACKEND + if (context->dartMethodPtr()->environment == nullptr) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_environment__': dart method (environment) is not registered."); + } + const char* env = context->dartMethodPtr()->environment(); + return JS_ParseJSON(ctx, env, strlen(env), ""); +#else + return JS_NewObject(ctx); +#endif +} + +static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* context = static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx)); + if (context->dartMethodPtr()->simulatePointer == nullptr) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); + } + + JSValue inputArrayValue = argv[0]; + if (!JS_IsObject(inputArrayValue)) { + return JS_ThrowTypeError(ctx, + "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); + } + + JSValue pointerValue = argv[1]; + if (!JS_IsNumber(pointerValue)) { + return JS_ThrowTypeError(ctx, + "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); + } + + uint32_t length; + JSValue lengthValue = JS_GetPropertyStr(ctx, inputArrayValue, "length"); + JS_ToUint32(ctx, &length, lengthValue); + JS_FreeValue(ctx, lengthValue); + + auto** mousePointerList = new MousePointer*[length]; + + for (int i = 0; i < length; i++) { + auto mouse = new MousePointer(); + JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); + mouse->contextId = context->contextId(); + JSValue xValue = JS_GetPropertyUint32(ctx, params, 0); + JSValue yValue = JS_GetPropertyUint32(ctx, params, 1); + JSValue changeValue = JS_GetPropertyUint32(ctx, params, 2); + + double x; + double y; + double change; + + JS_ToFloat64(ctx, &x, xValue); + JS_ToFloat64(ctx, &y, yValue); + JS_ToFloat64(ctx, &change, changeValue); + + mouse->x = x; + mouse->y = y; + mouse->change = change; + mousePointerList[i] = mouse; + + JS_FreeValue(ctx, params); + JS_FreeValue(ctx, xValue); + JS_FreeValue(ctx, yValue); + JS_FreeValue(ctx, changeValue); + } + + uint32_t pointer; + JS_ToUint32(ctx, &pointer, pointerValue); + + context->dartMethodPtr()->simulatePointer(mousePointerList, length, pointer); + + delete[] mousePointerList; + + return JS_NULL; +} + +static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* context = static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx)); + if (context->dartMethodPtr()->simulateInputText == nullptr) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); + } + + JSValue& charStringValue = argv[0]; + + if (!JS_IsString(charStringValue)) { + return JS_ThrowTypeError(ctx, + "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); + } + + std::unique_ptr<NativeString> nativeString = kraken::jsValueToNativeString(ctx, charStringValue); + void* p = static_cast<void*>(nativeString.get()); + context->dartMethodPtr()->simulateInputText(static_cast<NativeString*>(p)); + return JS_NULL; +}; + +static JSValue parseHTML(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* context = static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx)); + + if (argc == 1) { + std::string strHTML = AtomicString(ctx, argv[0]).ToStdString(); + auto* body = context->document()->body(); + HTMLParser::parseHTML(strHTML, body); + } + + return JS_NULL; +} + +static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* context = static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx)); + + JSValue globalErrorFunc = JS_GetPropertyStr(ctx, context->Global(), "triggerGlobalError"); + + if (JS_IsFunction(ctx, globalErrorFunc)) { + JSValue exception = JS_Call(ctx, globalErrorFunc, context->Global(), 0, nullptr); + context->HandleException(&exception); + JS_FreeValue(ctx, globalErrorFunc); + } + + return JS_NULL; +} + +struct ExecuteCallbackContext { + ExecuteCallbackContext() = delete; + + explicit ExecuteCallbackContext(ExecutingContext* context, ExecuteCallback executeCallback) + : executeCallback(executeCallback), context(context){}; + ExecuteCallback executeCallback; + ExecutingContext* context; +}; + +void KrakenTestContext::invokeExecuteTest(ExecuteCallback executeCallback) { + if (execute_test_callback_ == nullptr) { + return; + } + + auto done = [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, + JSValue* func_data) -> JSValue { + JSValue& statusValue = argv[0]; + JSValue proxyObject = func_data[0]; + auto* callbackContext = static_cast<ExecuteCallbackContext*>(JS_GetOpaque(proxyObject, 1)); + + if (!JS_IsString(statusValue)) { + return JS_ThrowTypeError(ctx, "failed to execute 'done': parameter 1 (status) is not a string"); + } + + KRAKEN_LOG(VERBOSE) << "Done.."; + + std::unique_ptr<NativeString> status = kraken::jsValueToNativeString(ctx, statusValue); + callbackContext->executeCallback(callbackContext->context->contextId(), status.get()); + JS_FreeValue(ctx, proxyObject); + return JS_NULL; + }; + auto* callbackContext = new ExecuteCallbackContext(context_, executeCallback); + execute_test_proxy_object_ = JS_NewObject(context_->ctx()); + JS_SetOpaque(execute_test_proxy_object_, callbackContext); + JSValue callbackData[]{execute_test_proxy_object_}; + JSValue callback = JS_NewCFunctionData(context_->ctx(), done, 0, 0, 1, callbackData); + + ScriptValue arguments[] = {ScriptValue(context_->ctx(), callback)}; + ScriptValue result = + execute_test_callback_->Invoke(context_->ctx(), ScriptValue::Empty(context_->ctx()), 1, arguments); + context_->HandleException(&result); + context_->DrainPendingPromiseJobs(); + JS_FreeValue(context_->ctx(), callback); + execute_test_callback_ = nullptr; +} + +KrakenTestContext::KrakenTestContext(ExecutingContext* context) + : context_(context), page_(static_cast<KrakenPage*>(context->owner())) { + page_->owner = this; + page_->disposeCallback = [](KrakenPage* bridge) { delete static_cast<KrakenTestContext*>(bridge->owner); }; + + std::initializer_list<MemberInstaller::FunctionConfig> functionConfig{ + {"__kraken_execute_test__", executeTest, 1}, + {"__kraken_match_image_snapshot__", matchImageSnapshot, 3}, + {"__kraken_environment__", environment, 0}, + {"__kraken_simulate_pointer__", simulatePointer, 1}, + {"__kraken_simulate_inputtext__", simulateInputText, 1}, + {"__kraken_trigger_global_error__", triggerGlobalError, 0}, + {"__kraken_parse_html__", parseHTML, 1}, + }; + + MemberInstaller::InstallFunctions(context, context->Global(), functionConfig); + initKrakenTestFramework(context); +} + +bool KrakenTestContext::evaluateTestScripts(const uint16_t* code, + size_t codeLength, + const char* sourceURL, + int startLine) { + if (!context_->IsValid()) + return false; + return context_->EvaluateJavaScript(code, codeLength, sourceURL, startLine); +} + +bool KrakenTestContext::parseTestHTML(const uint16_t* code, size_t codeLength) { + if (!context_->IsValid()) + return false; + std::string utf8Code = toUTF8(std::u16string(reinterpret_cast<const char16_t*>(code), codeLength)); + return page_->parseHTML(utf8Code.c_str(), utf8Code.length()); +} + +void KrakenTestContext::registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length) { + size_t i = 0; + + auto& dartMethodPtr = context_->dartMethodPtr(); + + dartMethodPtr->onJsError = reinterpret_cast<OnJSError>(methodBytes[i++]); + dartMethodPtr->matchImageSnapshot = reinterpret_cast<MatchImageSnapshot>(methodBytes[i++]); + dartMethodPtr->environment = reinterpret_cast<Environment>(methodBytes[i++]); + dartMethodPtr->simulatePointer = reinterpret_cast<SimulatePointer>(methodBytes[i++]); + dartMethodPtr->simulateInputText = reinterpret_cast<SimulateInputText>(methodBytes[i++]); + + assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); +} + +} // namespace kraken diff --git a/bridge/test/kraken_test_context.h b/bridge/test/kraken_test_context.h new file mode 100644 index 0000000000..ba3512a037 --- /dev/null +++ b/bridge/test/kraken_test_context.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H +#define KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H + +#include "bindings/qjs/qjs_function.h" +#include "core/executing_context.h" +#include "core/page.h" +#include "kraken_bridge_test.h" + +namespace kraken { + +struct ImageSnapShotContext { + JSValue callback; + ExecutingContext* context; + list_head link; +}; + +class KrakenTestContext final { + public: + explicit KrakenTestContext() = delete; + explicit KrakenTestContext(ExecutingContext* context); + + /// Evaluate JavaScript source code with build-in test frameworks, use in test only. + bool evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine); + bool parseTestHTML(const uint16_t* code, size_t codeLength); + void invokeExecuteTest(ExecuteCallback executeCallback); + void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length); + + std::shared_ptr<QJSFunction> execute_test_callback_{nullptr}; + JSValue execute_test_proxy_object_{JS_NULL}; + + private: + /// the pointer of JSContext, ownership belongs to JSContext + ExecutingContext* context_{nullptr}; + KrakenPage* page_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index d67ee971da..dd3200e937 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -2,14 +2,16 @@ * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ -#include "kraken_test_env.h" #include <sys/time.h> #include <vector> -#include "bindings/qjs/dom/event_target.h" -#include "dart_methods.h" -#include "include/kraken_bridge.h" + +#include "bindings/qjs/native_string_utils.h" +#include "core/dom/frame_request_callback_collection.h" +#include "core/frame/dom_timer.h" +#include "core/page.h" +#include "foundation/native_string.h" #include "kraken_bridge_test.h" -#include "page.h" +#include "kraken_test_env.h" #if defined(__linux__) || defined(__APPLE__) static int64_t get_time_ms(void) { @@ -26,10 +28,12 @@ static int64_t get_time_ms(void) { } #endif +namespace kraken { + typedef struct { struct list_head link; int64_t timeout; - DOMTimer* timer; + kraken::DOMTimer* timer; int32_t contextId; bool isInterval; AsyncCallback func; @@ -37,7 +41,7 @@ typedef struct { typedef struct { struct list_head link; - FrameCallback* callback; + kraken::FrameCallback* callback; int32_t contextId; AsyncRAFCallback handler; int32_t callbackId; @@ -48,22 +52,31 @@ typedef struct JSThreadState { std::unordered_map<int32_t, JSFrameCallback*> os_frameCallbacks; } JSThreadState; -static void unlink_timer(JSThreadState* ts, JSOSTimer* th) { - ts->os_timers.erase(th->timer->timerId()); +static void unlink_timer(JSThreadState* ts, int32_t timerId) { + ts->os_timers.erase(timerId); } 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) { +NativeString* TEST_invokeModule(void* callbackContext, + int32_t contextId, + NativeString* moduleName, + NativeString* method, + NativeString* params, + AsyncModuleCallback callback) { std::string module = nativeStringToStdString(moduleName); if (module == "throwError") { callback(callbackContext, contextId, nativeStringToStdString(method).c_str(), nullptr); } - return nullptr; + if (module == "MethodChannel") { + callback(callbackContext, contextId, nullptr, stringToNativeString("{\"result\": 1234}").release()); + } + + return stringToNativeString(module).release(); }; void TEST_requestBatchUpdate(int32_t contextId){}; @@ -72,9 +85,9 @@ void TEST_reloadApp(int32_t contextId) {} int32_t timerId = 0; -int32_t TEST_setTimeout(DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { - JSRuntime* rt = JS_GetRuntime(timer->ctx()); - auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(timer->ctx())); +int32_t TEST_setTimeout(kraken::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { + JSRuntime* rt = ScriptState::runtime(); + auto* context = timer->context(); JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(rt)); JSOSTimer* th = static_cast<JSOSTimer*>(js_mallocz(context->ctx(), sizeof(*th))); th->timeout = get_time_ms() + timeout; @@ -89,9 +102,9 @@ int32_t TEST_setTimeout(DOMTimer* timer, int32_t contextId, AsyncCallback callba return id; } -int32_t TEST_setInterval(DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { - JSRuntime* rt = JS_GetRuntime(timer->ctx()); - auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(timer->ctx())); +int32_t TEST_setInterval(kraken::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { + JSRuntime* rt = ScriptState::runtime(); + auto* context = timer->context(); JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(rt)); JSOSTimer* th = static_cast<JSOSTimer*>(js_mallocz(context->ctx(), sizeof(*th))); th->timeout = get_time_ms() + timeout; @@ -108,14 +121,14 @@ int32_t TEST_setInterval(DOMTimer* timer, int32_t contextId, AsyncCallback callb int32_t callbackId = 0; -uint32_t TEST_requestAnimationFrame(FrameCallback* frameCallback, int32_t contextId, AsyncRAFCallback handler) { - JSRuntime* rt = JS_GetRuntime(frameCallback->ctx()); - auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(frameCallback->ctx())); +uint32_t TEST_requestAnimationFrame(kraken::FrameCallback* frameCallback, int32_t contextId, AsyncRAFCallback handler) { + JSRuntime* rt = ScriptState::runtime(); + auto* context = frameCallback->context(); JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(rt)); JSFrameCallback* th = static_cast<JSFrameCallback*>(js_mallocz(context->ctx(), sizeof(*th))); th->handler = handler; th->callback = frameCallback; - th->contextId = context->getContextId(); + th->contextId = context->contextId(); int32_t id = callbackId++; th->callbackId = id; @@ -127,15 +140,15 @@ uint32_t TEST_requestAnimationFrame(FrameCallback* frameCallback, int32_t contex void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); - auto* context = page->getContext(); - JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(context->runtime())); + auto* context = page->GetExecutingContext(); + JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(ScriptState::runtime())); ts->os_frameCallbacks.erase(id); } void TEST_clearTimeout(int32_t contextId, int32_t timerId) { auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); - auto* context = page->getContext(); - JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(context->runtime())); + auto* context = page->GetExecutingContext(); + JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(ScriptState::runtime())); ts->os_timers.erase(timerId); } @@ -151,14 +164,17 @@ NativeString* TEST_platformBrightness(int32_t contextId) { return nullptr; } -void TEST_toBlob(void* callbackContext, int32_t contextId, AsyncBlobCallback blobCallback, int32_t elementId, double devicePixelRatio) {} +void TEST_toBlob(void* ptr, + int32_t contextId, + AsyncBlobCallback blobCallback, + int32_t elementId, + double devicePixelRatio) { + uint8_t bytes[5] = {0x01, 0x02, 0x03, 0x04, 0x05}; + blobCallback(ptr, contextId, nullptr, bytes, 5); +} void TEST_flushUICommand() {} -void TEST_initWindow(int32_t contextId, void* nativePtr) {} - -void TEST_initDocument(int32_t contextId, void* nativePtr) {} - void TEST_onJsLog(int32_t contextId, int32_t level, const char*) {} #if ENABLE_PROFILE @@ -181,13 +197,14 @@ std::unique_ptr<kraken::KrakenPage> TEST_init(OnJSError onJsError) { initJSPagePool(1024 * 1024); inited = true; }); + + TEST_mockDartMethods(contextId, onJsError); + initTestFramework(contextId); auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); - auto* context = page->getContext(); + auto* context = page->GetExecutingContext(); JSThreadState* th = new JSThreadState(); - JS_SetRuntimeOpaque(context->runtime(), th); - - TEST_mockDartMethods(onJsError); + JS_SetRuntimeOpaque(ScriptState::runtime(), th); return std::unique_ptr<kraken::KrakenPage>(page); } @@ -202,8 +219,8 @@ std::unique_ptr<kraken::KrakenPage> TEST_allocateNewPage() { return std::unique_ptr<kraken::KrakenPage>(static_cast<kraken::KrakenPage*>(getPage(newContextId))); } -static bool jsPool(ExecutionContext* context) { - JSRuntime* rt = context->runtime(); +static bool jsPool(kraken::ExecutingContext* context) { + JSRuntime* rt = ScriptState::runtime(); JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(rt)); int64_t cur_time, delay; struct list_head* el; @@ -225,8 +242,9 @@ static bool jsPool(ExecutionContext* context) { func(th->timer, th->contextId, nullptr); } else { th->func = nullptr; + int32_t timerId = th->timer->timerId(); func(th->timer, th->contextId, nullptr); - unlink_timer(ts, th); + unlink_timer(ts, timerId); } return false; @@ -248,50 +266,15 @@ static bool jsPool(ExecutionContext* context) { return false; } -void TEST_runLoop(ExecutionContext* context) { +void TEST_runLoop(kraken::ExecutingContext* context) { for (;;) { - context->drainPendingPromiseJobs(); + context->DrainPendingPromiseJobs(); if (jsPool(context)) break; } } -void TEST_dispatchEvent(int32_t contextId, EventTargetInstance* eventTarget, const std::string type) { - NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); - auto nativeEventType = stringToNativeString(type); - NativeString* rawEventType = nativeEventType.release(); - -#if ANDROID_32_BIT - NativeEvent* nativeEvent = new NativeEvent{reinterpret_cast<int64_t>(rawEventType)}; -#else - NativeEvent* nativeEvent = new NativeEvent{rawEventType}; -#endif - - RawEvent* rawEvent = new RawEvent{reinterpret_cast<uint64_t*>(nativeEvent)}; - - NativeEventTarget::dispatchEventImpl(contextId, nativeEventTarget, rawEventType, rawEvent, false); -} - -void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv) {} - -std::unordered_map<int32_t, std::shared_ptr<UnitTestEnv>> unitTestEnvMap; -std::shared_ptr<UnitTestEnv> TEST_getEnv(int32_t contextUniqueId) { - if (unitTestEnvMap.count(contextUniqueId) == 0) { - unitTestEnvMap[contextUniqueId] = std::make_shared<UnitTestEnv>(); - } - - return unitTestEnvMap[contextUniqueId]; -} - -void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback) { - if (unitTestEnvMap.count(contextUniqueId) == 0) { - unitTestEnvMap[contextUniqueId] = std::make_shared<UnitTestEnv>(); - } - - unitTestEnvMap[contextUniqueId]->onEventTargetDisposed = callback; -} - -void TEST_mockDartMethods(OnJSError onJSError) { +void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { std::vector<uint64_t> mockMethods{ reinterpret_cast<uint64_t>(TEST_invokeModule), reinterpret_cast<uint64_t>(TEST_requestBatchUpdate), @@ -301,11 +284,8 @@ void TEST_mockDartMethods(OnJSError onJSError) { reinterpret_cast<uint64_t>(TEST_clearTimeout), reinterpret_cast<uint64_t>(TEST_requestAnimationFrame), reinterpret_cast<uint64_t>(TEST_cancelAnimationFrame), - reinterpret_cast<uint64_t>(TEST_getScreen), reinterpret_cast<uint64_t>(TEST_toBlob), reinterpret_cast<uint64_t>(TEST_flushUICommand), - reinterpret_cast<uint64_t>(TEST_initWindow), - reinterpret_cast<uint64_t>(TEST_initDocument), }; #if ENABLE_PROFILE @@ -316,5 +296,38 @@ void TEST_mockDartMethods(OnJSError onJSError) { mockMethods.emplace_back(reinterpret_cast<uint64_t>(onJSError)); mockMethods.emplace_back(reinterpret_cast<uint64_t>(TEST_onJsLog)); - registerDartMethods(mockMethods.data(), mockMethods.size()); + registerDartMethods(contextId, mockMethods.data(), mockMethods.size()); } + +} // namespace kraken + +// void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type) { +// NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); +// auto nativeEventType = stringToNativeString(type); +// NativeString* rawEventType = nativeEventType.release(); +// +// NativeEvent* nativeEvent = new NativeEvent{rawEventType}; +// +// RawEvent* rawEvent = new RawEvent{reinterpret_cast<uint64_t*>(nativeEvent)}; +// +// NativeEventTarget::dispatchEventImpl(contextId, nativeEventTarget, rawEventType, rawEvent, false); +//} +// +// void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv) {} +// +// std::unordered_map<int32_t, std::shared_ptr<UnitTestEnv>> unitTestEnvMap; +// std::shared_ptr<UnitTestEnv> TEST_getEnv(int32_t contextUniqueId) { +// if (unitTestEnvMap.count(contextUniqueId) == 0) { +// unitTestEnvMap[contextUniqueId] = std::make_shared<UnitTestEnv>(); +// } +// +// return unitTestEnvMap[contextUniqueId]; +//} +// +// void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback) { +// if (unitTestEnvMap.count(contextUniqueId) == 0) { +// unitTestEnvMap[contextUniqueId] = std::make_shared<UnitTestEnv>(); +// } +// +// unitTestEnvMap[contextUniqueId]->onEventTargetDisposed = callback; +//} diff --git a/bridge/test/kraken_test_env.h b/bridge/test/kraken_test_env.h index 4bc17084a5..daccbd1927 100644 --- a/bridge/test/kraken_test_env.h +++ b/bridge/test/kraken_test_env.h @@ -6,31 +6,32 @@ #define KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ #include <memory> -#include "bindings/qjs/bom/timer.h" -#include "bindings/qjs/dom/event_target.h" -#include "bindings/qjs/dom/frame_request_callback_collection.h" +#include "core/dart_methods.h" +#include "core/executing_context.h" +#include "core/page.h" #include "foundation/logging.h" -#include "include/dart_methods.h" -#include "page.h" +// +//// Trigger a callbacks before GC free the eventTargets. +// using TEST_OnEventTargetDisposed = void (*)(binding::qjs::EventTargetInstance* eventTargetInstance); +// struct UnitTestEnv { +// TEST_OnEventTargetDisposed onEventTargetDisposed{nullptr}; +//}; +// +//// Mock dart methods and add async timer to emulate kraken environment in C++ unit test. +// -using namespace kraken::binding::qjs; +namespace kraken { -// Trigger a callbacks before GC free the eventTargets. -using TEST_OnEventTargetDisposed = void (*)(kraken::binding::qjs::EventTargetInstance* eventTargetInstance); -struct UnitTestEnv { - TEST_OnEventTargetDisposed onEventTargetDisposed{nullptr}; -}; +std::unique_ptr<KrakenPage> TEST_init(OnJSError onJsError); +std::unique_ptr<KrakenPage> TEST_init(); +std::unique_ptr<KrakenPage> TEST_allocateNewPage(); +void TEST_runLoop(ExecutingContext* context); +void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError); -// Mock dart methods and add async timer to emulate kraken environment in C++ unit test. - -std::unique_ptr<kraken::KrakenPage> TEST_init(OnJSError onJsError); -std::unique_ptr<kraken::KrakenPage> TEST_init(); -std::unique_ptr<kraken::KrakenPage> TEST_allocateNewPage(); -void TEST_runLoop(ExecutionContext* context); -void TEST_dispatchEvent(int32_t contextId, EventTargetInstance* eventTarget, const std::string type); -void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); -void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); -void TEST_mockDartMethods(OnJSError onJSError); -std::shared_ptr<UnitTestEnv> TEST_getEnv(int32_t contextUniqueId); +} // namespace kraken +// void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type); +// void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); +// void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); +// std::shared_ptr<UnitTestEnv> TEST_getEnv(int32_t contextUniqueId); #endif // KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ diff --git a/bridge/test/run_integration_test.cc b/bridge/test/run_integration_test.cc index e6eaf5a9e5..bbd4c7672a 100644 --- a/bridge/test/run_integration_test.cc +++ b/bridge/test/run_integration_test.cc @@ -3,10 +3,12 @@ */ #include <fstream> +#include "foundation/logging.h" #include "gtest/gtest.h" #include "kraken_bridge_test.h" #include "kraken_test_env.h" -#include "page.h" + +using namespace kraken; std::string readTestSpec() { std::string filepath = std::string(SPEC_FILE_PATH) + "/../integration_tests/.specs/core.build.js"; @@ -30,12 +32,15 @@ std::string readTestSpec() { // Very useful to fix bridge bugs. TEST(IntegrationTest, runSpecs) { auto bridge = TEST_init(); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); std::string code = readTestSpec(); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - executeTest(context->getContextId(), [](int32_t contextId, NativeString* status) -> void* { KRAKEN_LOG(VERBOSE) << "done"; }); + executeTest(context->contextId(), [](int32_t contextId, void* status) -> void* { + KRAKEN_LOG(VERBOSE) << "done"; + return nullptr; + }); TEST_runLoop(context); } diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index a7aa81b38f..401171b24d 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -10,28 +10,37 @@ add_subdirectory(./third_party/googletest) add_subdirectory(./third_party/benchmark) list(APPEND KRAKEN_TEST_SOURCE - page_test.cc - page_test.h + test/kraken_test_context.cc + test/kraken_test_context.h ) list(APPEND KRAKEN_UNIT_TEST_SOURCE ./test/kraken_test_env.cc ./test/kraken_test_env.h - ./bindings/qjs/js_context_test.cc - ./bindings/qjs/bom/timer_test.cc - ./bindings/qjs/bom/console_test.cc - ./bindings/qjs/qjs_patch_test.cc - ./bindings/qjs/host_object_test.cc - ./bindings/qjs/host_class_test.cc - ./bindings/qjs/dom/event_target_test.cc - ./bindings/qjs/module_manager_test.cc - ./bindings/qjs/dom/node_test.cc - ./bindings/qjs/dom/event_test.cc - ./bindings/qjs/dom/element_test.cc - ./bindings/qjs/dom/document_test.cc - ./bindings/qjs/dom/text_node_test.cc - ./bindings/qjs/bom/window_test.cc - ./bindings/qjs/dom/custom_event_test.cc - ./bindings/qjs/module_manager_test.cc + ./bindings/qjs/atomic_string_test.cc + ./bindings/qjs/script_value_test.cc + ./core/executing_context_test.cc + ./core/frame/console_test.cc + ./core/frame/module_manager_test.cc + ./core/dom/events/event_target_test.cc + ./core/dom/document_test.cc + ./core/dom/node_test.cc + ./core/dom/element_test.cc + ./core/frame/dom_timer_test.cc + ./core/frame/window_test.cc + ./core/css/legacy/css_style_declaration_test.cc + # ./bindings/qjs/bom/timer_test.cc +# ./bindings/qjs/qjs_patch_test.cc +# ./bindings/qjs/garbage_collected_test.cc +# ./bindings/qjs/dom/event_target_test.cc +# ./bindings/qjs/module_manager_test.cc +# ./bindings/qjs/dom/node_test.cc +# ./bindings/qjs/dom/event_test.cc +# ./bindings/qjs/dom/element_test.cc +# ./bindings/qjs/dom/document_test.cc +# ./bindings/qjs/dom/text_node_test.cc +# ./bindings/qjs/bom/window_test.cc +# ./bindings/qjs/dom/custom_event_test.cc +# ./bindings/qjs/module_manager_test.cc ) ### kraken_unit_test executable @@ -95,6 +104,7 @@ add_library(kraken_test SHARED ${KRAKEN_TEST_SOURCE}) target_link_libraries(kraken_test PRIVATE ${BRIDGE_LINK_LIBS} kraken) target_include_directories(kraken_test PRIVATE ${BRIDGE_INCLUDE} + ./test ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ./include) if (DEFINED ENV{LIBRARY_OUTPUT_DIR}) diff --git a/bridge/third_party/quickjs/quickjs-external-atom.h b/bridge/third_party/quickjs/quickjs-external-atom.h new file mode 100644 index 0000000000..4a63a4c243 --- /dev/null +++ b/bridge/third_party/quickjs/quickjs-external-atom.h @@ -0,0 +1,7 @@ +// External static string atoms defined by users. + +#ifdef DEF + + + +#endif /* DEF */ diff --git a/bridge/third_party/quickjs/quickjs.c b/bridge/third_party/quickjs/quickjs.c index cb09d5f3e4..1b35569d3b 100644 --- a/bridge/third_party/quickjs/quickjs.c +++ b/bridge/third_party/quickjs/quickjs.c @@ -67,12 +67,6 @@ #define CONFIG_PRINTF_RNDN #endif -/* define to include Atomics.* operations which depend on the OS - threads */ -#if !defined(EMSCRIPTEN) -#define CONFIG_ATOMICS -#endif - #if !defined(EMSCRIPTEN) /* enable stack limitation */ #define CONFIG_STACK_CHECK @@ -949,21 +943,7 @@ struct JSObject { } u; /* byte sizes: 40/48/72 */ }; -enum { - __JS_ATOM_NULL = JS_ATOM_NULL, -#define DEF(name, str) JS_ATOM_ ## name, -#include "quickjs-atom.h" -#undef DEF - JS_ATOM_END, -}; -#define JS_ATOM_LAST_KEYWORD JS_ATOM_super -#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield -static const char js_atom_init[] = -#define DEF(name, str) str "\0" -#include "quickjs-atom.h" -#undef DEF -; typedef enum OPCodeFormat { #define FMT(f) OP_FMT_ ## f, @@ -2377,12 +2357,6 @@ static inline BOOL is_math_mode(JSContext *ctx) } #endif -/* JSAtom support */ - -#define JS_ATOM_TAG_INT (1U << 31) -#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1) -#define JS_ATOM_MAX ((1U << 30) - 1) - /* return the max count from the hash size */ #define JS_ATOM_COUNT_RESIZE(n) ((n) * 2) @@ -2592,14 +2566,15 @@ static int JS_InitAtoms(JSRuntime *rt) rt->atom_count = 0; rt->atom_size = 0; rt->atom_free_index = 0; - if (JS_ResizeAtomHash(rt, 256)) /* there are at least 195 predefined atoms */ + if (JS_ResizeAtomHash(rt, 1024)) /* there are at least 195 predefined atoms */ return -1; p = js_atom_init; + for(i = 1; i < JS_ATOM_END; i++) { if (i == JS_ATOM_Private_brand) atom_type = JS_ATOM_TYPE_PRIVATE; - else if (i >= JS_ATOM_Symbol_toPrimitive) + else if (i >= JS_ATOM_Symbol_toPrimitive && i <= JS_ATOM_Symbol_asyncIterator) atom_type = JS_ATOM_TYPE_SYMBOL; else atom_type = JS_ATOM_TYPE_STRING; @@ -2708,7 +2683,8 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) } /* try and locate an already registered atom */ len = str->len; - h = hash_string(str, atom_type); + /* only in extreme case will str has zero hash, we accept extra hash calc in that case. */ + h = str->hash != 0 ? str->hash : hash_string(str, atom_type); h &= JS_ATOM_HASH_MASK; h1 = h & (rt->atom_hash_size - 1); i = rt->atom_hash[h1]; @@ -2743,7 +2719,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092 preallocating space for predefined atoms (at least 195). */ - new_size = max_int(211, rt->atom_size * 3 / 2); + new_size = max_int(1066, rt->atom_size * 3 / 2); if (new_size > JS_ATOM_MAX) goto fail; /* XXX: should use realloc2 to use slack space */ @@ -35971,7 +35947,7 @@ static JSAtom find_atom(JSContext *ctx, const char *name) len = strlen(name) - 1; /* We assume 8 bit non null strings, which is the case for these symbols */ - for(atom = JS_ATOM_Symbol_toPrimitive; atom < JS_ATOM_END; atom++) { + for(atom = JS_ATOM_Symbol_toPrimitive; atom <= JS_ATOM_Symbol_asyncIterator; atom++) { JSAtomStruct *p = ctx->rt->atom_array[atom]; JSString *str = p; if (str->len == len && !memcmp(str->u.str8, name, len)) @@ -51044,7 +51020,7 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) ctx->class_proto[JS_CLASS_SYMBOL]); JS_SetPropertyFunctionList(ctx, obj, js_symbol_funcs, countof(js_symbol_funcs)); - for(i = JS_ATOM_Symbol_toPrimitive; i < JS_ATOM_END; i++) { + for(i = JS_ATOM_Symbol_toPrimitive; i <= JS_ATOM_Symbol_asyncIterator; i++) { char buf[ATOM_GET_STR_BUF_SIZE]; const char *str, *p; str = JS_AtomGetStr(ctx, buf, sizeof(buf), i); diff --git a/bridge/third_party/quickjs/quickjs.h b/bridge/third_party/quickjs/quickjs.h index d4a5cd3114..c7b264ef27 100644 --- a/bridge/third_party/quickjs/quickjs.h +++ b/bridge/third_party/quickjs/quickjs.h @@ -126,7 +126,7 @@ static inline JS_BOOL JS_VALUE_IS_NAN(JSValue v) { return 0; } - + #elif defined(JS_NAN_BOXING) typedef uint64_t JSValue; @@ -191,9 +191,15 @@ static inline JS_BOOL JS_VALUE_IS_NAN(JSValue v) tag = JS_VALUE_GET_TAG(v); return tag == (JS_NAN >> 32); } - + #else /* !JS_NAN_BOXING */ +/* define to include Atomics.* operations which depend on the OS + threads */ +#if !defined(EMSCRIPTEN) +#define CONFIG_ATOMICS +#endif + typedef union JSValueUnion { int32_t int32; double float64; @@ -417,6 +423,25 @@ void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt); /* atom support */ #define JS_ATOM_NULL 0 +#define JS_ATOM_TAG_INT (1U << 31) +#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1) +#define JS_ATOM_MAX ((1U << 30) - 1) + +enum { + __JS_ATOM_NULL = JS_ATOM_NULL, +#define DEF(name, str) JS_ATOM_ ## name, +#include "quickjs-atom.h" +#undef DEF + JS_ATOM_END, +}; +#define JS_ATOM_LAST_KEYWORD JS_ATOM_super +#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield + +static const char js_atom_init[] = +#define DEF(name, str) str "\0" +#include "quickjs-atom.h" +#undef DEF +; JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len); JSAtom JS_NewAtom(JSContext *ctx, const char *str); @@ -957,7 +982,7 @@ static inline JSValue JS_NewCFunctionMagic(JSContext *ctx, JSCFunctionMagic *fun { return JS_NewCFunction2(ctx, (JSCFunction *)func, name, length, cproto, magic); } -void JS_SetConstructor(JSContext *ctx, JSValueConst func_obj, +void JS_SetConstructor(JSContext *ctx, JSValueConst func_obj, JSValueConst proto); /* C property definition */ diff --git a/bridge/tsconfig.json b/bridge/tsconfig.json new file mode 100644 index 0000000000..9ebac78b8d --- /dev/null +++ b/bridge/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "lib": [ + "es6", + "es7" + ], + "allowJs": false, + "moduleResolution": "node", + "forceConsistentCasingInFileNames": false, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": false, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "declaration": false, + "outDir": "./dist" + } +} diff --git a/integration_tests/lib/bridge/from_native.dart b/integration_tests/lib/bridge/from_native.dart index 3a3eb69327..cfcdd9794d 100644 --- a/integration_tests/lib/bridge/from_native.dart +++ b/integration_tests/lib/bridge/from_native.dart @@ -140,15 +140,15 @@ final List<int> _dartNativeMethods = [ _nativeSimulateInputText.address ]; -typedef Native_RegisterTestEnvDartMethods = Void Function(Pointer<Uint64> methodBytes, Int32 length); -typedef Dart_RegisterTestEnvDartMethods = void Function(Pointer<Uint64> methodBytes, int length); +typedef Native_RegisterTestEnvDartMethods = Void Function(Int32 contextId, Pointer<Uint64> methodBytes, Int32 length); +typedef Dart_RegisterTestEnvDartMethods = void Function(int contextId, Pointer<Uint64> methodBytes, int length); final Dart_RegisterTestEnvDartMethods _registerTestEnvDartMethods = KrakenDynamicLibrary.ref.lookup<NativeFunction<Native_RegisterTestEnvDartMethods>>('registerTestEnvDartMethods').asFunction(); -void registerDartTestMethodsToCpp() { +void registerDartTestMethodsToCpp(int contextId) { Pointer<Uint64> bytes = malloc.allocate<Uint64>(sizeOf<Uint64>() * _dartNativeMethods.length); Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); - _registerTestEnvDartMethods(bytes, _dartNativeMethods.length); + _registerTestEnvDartMethods(contextId, bytes, _dartNativeMethods.length); } diff --git a/integration_tests/lib/main.dart b/integration_tests/lib/main.dart index b4da5d49d6..66a01c49fb 100644 --- a/integration_tests/lib/main.dart +++ b/integration_tests/lib/main.dart @@ -96,10 +96,9 @@ void main() async { testTextInput = TestTextInput(); WidgetsBinding.instance!.addPostFrameCallback((_) async { - registerDartTestMethodsToCpp(); int contextId = kraken.controller!.view.contextId; - initTestFramework(contextId); + registerDartTestMethodsToCpp(contextId); addJSErrorListener(contextId, print); // Preload load test cases String code = spec.readAsStringSync(); diff --git a/integration_tests/lib/plugin.dart b/integration_tests/lib/plugin.dart index 4e9b031acf..39d68d8048 100644 --- a/integration_tests/lib/plugin.dart +++ b/integration_tests/lib/plugin.dart @@ -95,8 +95,6 @@ void main() async { )); WidgetsBinding.instance!.addPostFrameCallback((_) async { - registerDartTestMethodsToCpp(); - List<Future<String>> testResults = []; for (int i = 0; i < widgets.length; i++) { @@ -105,6 +103,7 @@ void main() async { addJSErrorListener(contextId, (String err) { print(err); }); + registerDartTestMethodsToCpp(contextId); Map<String, String> payload = allSpecsPayload[i]; diff --git a/integration_tests/webpack.config.js b/integration_tests/webpack.config.js index 61813e5dd8..cbeb1ae476 100644 --- a/integration_tests/webpack.config.js +++ b/integration_tests/webpack.config.js @@ -9,10 +9,11 @@ const resetRuntimePath = path.join(context, 'runtime/reset'); const buildPath = path.join(context, '.specs'); const testPath = path.join(context, 'specs'); const snapshotPath = path.join(context, 'snapshots'); -const coreSpecFiles = glob.sync('specs/**/*.{js,jsx,ts,tsx,html}', { - cwd: context, - ignore: ['node_modules/**'], -}).map((file) => './' + file).filter(name => name.indexOf('plugins') < 0); +// const coreSpecFiles = glob.sync('specs/**/*.{js,jsx,ts,tsx,html}', { +// cwd: context, +// ignore: ['node_modules/**'], +// }).map((file) => './' + file).filter(name => name.indexOf('plugins') < 0); +const coreSpecFiles = []; const pluginSpecFiles = glob.sync('specs/plugins/**/*.{js,jsx,ts,tsx}', { cwd: context, diff --git a/kraken/lib/src/bridge/binding.dart b/kraken/lib/src/bridge/binding.dart index 93d545d8d0..7f3e87dc2f 100644 --- a/kraken/lib/src/bridge/binding.dart +++ b/kraken/lib/src/bridge/binding.dart @@ -24,7 +24,7 @@ typedef NativeAsyncAnonymousFunctionCallback = Void Function( typedef DartAsyncAnonymousFunctionCallback = void Function(Pointer<Void> callbackContext, Pointer<NativeValue> nativeValue, int contextId, Pointer<Utf8> errmsg); // This function receive calling from binding side. -void _invokeBindingMethod(Pointer<Void> nativeBindingObject, Pointer<NativeValue> returnValue, Pointer<NativeString> nativeMethod, int argc, Pointer<NativeValue> argv) { +void _invokeBindingMethodFromNativeImpl(Pointer<NativeBindingObject> nativeBindingObject, Pointer<NativeValue> returnValue, Pointer<NativeString> nativeMethod, int argc, Pointer<NativeValue> argv) { String method = nativeStringToString(nativeMethod); List<dynamic> values = List.generate(argc, (i) { Pointer<NativeValue> nativeValue = argv.elementAt(i); @@ -63,7 +63,7 @@ void _invokeBindingMethod(Pointer<Void> nativeBindingObject, Pointer<NativeValue toNativeValue(returnValue, null); } else { // @TODO: Should not share the same binding method, and separate by magic. - BindingObject bindingObject = BindingBridge.getBindingObject(nativeBindingObject.cast<NativeBindingObject>()); + BindingObject bindingObject = BindingBridge.getBindingObject(nativeBindingObject); var result; try { if (method == GetPropertyMagic && argc == 1) { @@ -82,18 +82,37 @@ void _invokeBindingMethod(Pointer<Void> nativeBindingObject, Pointer<NativeValue } } +List prepareDispatchEventArguments(Event event) { + Pointer<Void> rawEvent = event.toRaw().cast<Void>(); + bool isCustomEvent = event is CustomEvent; + return [event.type, rawEvent, isCustomEvent ? 1 : 0]; +} + // Dispatch the event to the binding side. -void _dispatchBindingEvent(Event event) { +void _dispatchEventToNative(Event event) { Pointer<NativeBindingObject>? pointer = event.currentTarget?.pointer; int? contextId = event.target?.contextId; if (contextId != null && pointer != null) { - emitUIEvent(contextId, pointer, event); + // Call methods implements at C++ side. + DartInvokeBindingMethodsFromDart f = pointer.ref.invokeBindingMethodFromDart.asFunction(); + + List dispatchEventArguments = prepareDispatchEventArguments(event); + Pointer<NativeString> method = stringToNativeString('dispatchEvent'); + Pointer<NativeValue> allocatedNativeArguments = makeNativeValueArguments(dispatchEventArguments); + + f(pointer, nullptr, method, dispatchEventArguments.length, allocatedNativeArguments); + + // Free the allocated arguments. + malloc.free(method); + malloc.free(allocatedNativeArguments); + + } } abstract class BindingBridge { - static final Pointer<NativeFunction<NativeInvokeBindingMethod>> _nativeInvokeBindingMethod = Pointer.fromFunction(_invokeBindingMethod); - static Pointer<NativeFunction<NativeInvokeBindingMethod>> get nativeInvokeBindingMethod => _nativeInvokeBindingMethod; + static final Pointer<NativeFunction<InvokeBindingsMethodsFromNative>> _invokeBindingMethodFromNative = Pointer.fromFunction(_invokeBindingMethodFromNativeImpl); + static Pointer<NativeFunction<InvokeBindingsMethodsFromNative>> get nativeInvokeBindingMethod => _invokeBindingMethodFromNative; static final SplayTreeMap<int, BindingObject> _nativeObjects = SplayTreeMap(); @@ -106,28 +125,18 @@ abstract class BindingBridge { } static void _bindObject(BindingObject object) { - Pointer? nativeBindingObject = castToType<Pointer?>(object.pointer); + Pointer<NativeBindingObject>? nativeBindingObject = object.pointer; if (nativeBindingObject != null) { _nativeObjects[nativeBindingObject.address] = object; - if (nativeBindingObject is Pointer<NativeBindingObject>) { - nativeBindingObject.ref.invokeBindingMethod = _nativeInvokeBindingMethod; - } else if (nativeBindingObject is Pointer<NativeCanvasRenderingContext2D>) { - // @TODO: Remove it. - nativeBindingObject.ref.invokeBindingMethod = _nativeInvokeBindingMethod; - } + nativeBindingObject.ref.invokeBindingMethodFromNative = _invokeBindingMethodFromNative; } } static void _unbindObject(BindingObject object) { - Pointer? nativeBindingObject = castToType<Pointer?>(object.pointer); + Pointer<NativeBindingObject>? nativeBindingObject = object.pointer; if (nativeBindingObject != null) { _nativeObjects.remove(nativeBindingObject.address); - if (nativeBindingObject is Pointer<NativeBindingObject>) { - nativeBindingObject.ref.invokeBindingMethod = nullptr; - } else if (nativeBindingObject is Pointer<NativeCanvasRenderingContext2D>) { - // @TODO: Remove it. - nativeBindingObject.ref.invokeBindingMethod = nullptr; - } + nativeBindingObject.ref.invokeBindingMethodFromNative = nullptr; } } @@ -144,20 +153,20 @@ abstract class BindingBridge { static void listenEvent(EventTarget target, String type) { assert(_debugShouldNotListenMultiTimes(target, type), 'Failed to listen event \'$type\' for $target, for which is already bound.'); - target.addEventListener(type, _dispatchBindingEvent); + target.addEventListener(type, _dispatchEventToNative); } static void unlistenEvent(EventTarget target, String type) { assert(_debugShouldNotUnlistenEmpty(target, type), 'Failed to unlisten event \'$type\' for $target, for which is already unbound.'); - target.removeEventListener(type, _dispatchBindingEvent); + target.removeEventListener(type, _dispatchEventToNative); } static bool _debugShouldNotListenMultiTimes(EventTarget target, String type) { Map<String, List<EventHandler>> eventHandlers = target.getEventHandlers(); List<EventHandler>? handlers = eventHandlers[type]; if (handlers != null) { - return !handlers.contains(_dispatchBindingEvent); + return !handlers.contains(_dispatchEventToNative); } return true; } @@ -166,7 +175,7 @@ abstract class BindingBridge { Map<String, List<EventHandler>> eventHandlers = target.getEventHandlers(); List<EventHandler>? handlers = eventHandlers[type]; if (handlers != null) { - return handlers.contains(_dispatchBindingEvent); + return handlers.contains(_dispatchEventToNative); } return false; } diff --git a/kraken/lib/src/bridge/bridge.dart b/kraken/lib/src/bridge/bridge.dart index 47d93d66ea..0b08bb81f9 100644 --- a/kraken/lib/src/bridge/bridge.dart +++ b/kraken/lib/src/bridge/bridge.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/scheduler.dart'; import 'package:kraken/module.dart'; +import 'package:kraken/launcher.dart'; import 'binding.dart'; import 'from_native.dart'; @@ -19,14 +20,11 @@ int kKrakenJSPagePoolSize = 1024; bool _firstView = true; /// Init bridge -int initBridge() { +int initBridge(KrakenViewController view) { if (kProfileMode) { PerformanceTiming.instance().mark(PERF_BRIDGE_REGISTER_DART_METHOD_START); } - // Register methods first to share ptrs for bridge polyfill. - registerDartMethodsToCpp(); - // Setup binding bridge. BindingBridge.setup(); @@ -42,8 +40,7 @@ int initBridge() { Future.microtask(() { // Port flutter's frame callback into bridge. SchedulerBinding.instance!.addPersistentFrameCallback((_) { - flushUICommand(); - flushUICommandCallback(); + flushUICommand(view); }); }); } @@ -59,5 +56,8 @@ int initBridge() { } } + // Register methods first to share ptrs for bridge polyfill. + registerDartMethodsToCpp(contextId); + return contextId; } diff --git a/kraken/lib/src/bridge/from_native.dart b/kraken/lib/src/bridge/from_native.dart index c742ebebbb..c0cd1e9c6b 100644 --- a/kraken/lib/src/bridge/from_native.dart +++ b/kraken/lib/src/bridge/from_native.dart @@ -5,7 +5,6 @@ import 'dart:convert'; import 'dart:ffi'; import 'dart:typed_data'; -import 'dart:ui'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart'; @@ -47,6 +46,12 @@ int doubleToUint64(double value) { return byteData.getUint64(0); } +int doubleToInt64(double value) { + var byteData = ByteData(8); + byteData.setFloat64(0, value); + return byteData.getInt64(0); +} + double uInt64ToDouble(int value) { var byteData = ByteData(8); byteData.setInt64(0, value); @@ -279,15 +284,6 @@ void _cancelAnimationFrame(int contextId, int timerId) { final Pointer<NativeFunction<NativeCancelAnimationFrame>> _nativeCancelAnimationFrame = Pointer.fromFunction(_cancelAnimationFrame); -typedef NativeGetScreen = Pointer<Void> Function(); - -Pointer<Void> _getScreen() { - Size size = window.physicalSize; - return createScreen(size.width / window.devicePixelRatio, size.height / window.devicePixelRatio); -} - -final Pointer<NativeFunction<NativeGetScreen>> _nativeGetScreen = Pointer.fromFunction(_getScreen); - typedef NativeAsyncBlobCallback = Void Function( Pointer<Void> callbackContext, Int32 contextId, Pointer<Utf8>, Pointer<Uint8>, Int32); typedef DartAsyncBlobCallback = void Function( @@ -313,14 +309,14 @@ void _toBlob(Pointer<Void> callbackContext, int contextId, final Pointer<NativeFunction<NativeToBlob>> _nativeToBlob = Pointer.fromFunction(_toBlob); -typedef NativeFlushUICommand = Void Function(); -typedef DartFlushUICommand = void Function(); +typedef NativeFlushUICommand = Void Function(Int32 contextId); +typedef DartFlushUICommand = void Function(int contextId); -void _flushUICommand() { +void _flushUICommand(int contextId) { if (kProfileMode) { PerformanceTiming.instance().mark(PERF_DOM_FLUSH_UI_COMMAND_START); } - flushUICommand(); + flushUICommandWithContextId(contextId); if (kProfileMode) { PerformanceTiming.instance().mark(PERF_DOM_FLUSH_UI_COMMAND_END); } @@ -328,24 +324,6 @@ void _flushUICommand() { final Pointer<NativeFunction<NativeFlushUICommand>> _nativeFlushUICommand = Pointer.fromFunction(_flushUICommand); -typedef NativeInitWindow = Void Function(Int32 contextId, Pointer<NativeBindingObject> nativePtr); -typedef DartInitWindow = void Function(int contextId, Pointer<NativeBindingObject> nativePtr); - -void _initWindow(int contextId, Pointer<NativeBindingObject> nativePtr) { - KrakenViewController.windowNativePtrMap[contextId] = nativePtr; -} - -final Pointer<NativeFunction<NativeInitWindow>> _nativeInitWindow = Pointer.fromFunction(_initWindow); - -typedef NativeInitDocument = Void Function(Int32 contextId, Pointer<NativeBindingObject> nativePtr); -typedef DartInitDocument = void Function(int contextId, Pointer<NativeBindingObject> nativePtr); - -void _initDocument(int contextId, Pointer<NativeBindingObject> nativePtr) { - KrakenViewController.documentNativePtrMap[contextId] = nativePtr; -} - -final Pointer<NativeFunction<NativeInitDocument>> _nativeInitDocument = Pointer.fromFunction(_initDocument); - typedef NativePerformanceGetEntries = Pointer<NativePerformanceEntryList> Function(Int32 contextId); typedef DartPerformanceGetEntries = Pointer<NativePerformanceEntryList> Function(int contextId); @@ -396,27 +374,24 @@ final List<int> _dartNativeMethods = [ _nativeClearTimeout.address, _nativeRequestAnimationFrame.address, _nativeCancelAnimationFrame.address, - _nativeGetScreen.address, _nativeToBlob.address, _nativeFlushUICommand.address, - _nativeInitWindow.address, - _nativeInitDocument.address, _nativeGetEntries.address, _nativeOnJsError.address, _nativeOnJsLog.address, ]; -typedef NativeRegisterDartMethods = Void Function(Pointer<Uint64> methodBytes, Int32 length); -typedef DartRegisterDartMethods = void Function(Pointer<Uint64> methodBytes, int length); +typedef NativeRegisterDartMethods = Void Function(Int32 contextId, Pointer<Uint64> methodBytes, Int32 length); +typedef DartRegisterDartMethods = void Function(int contextId, Pointer<Uint64> methodBytes, int length); final DartRegisterDartMethods _registerDartMethods = KrakenDynamicLibrary .ref .lookup<NativeFunction<NativeRegisterDartMethods>>('registerDartMethods') .asFunction(); -void registerDartMethodsToCpp() { +void registerDartMethodsToCpp(int contextId) { Pointer<Uint64> bytes = malloc.allocate<Uint64>(sizeOf<Uint64>() * _dartNativeMethods.length); Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); - _registerDartMethods(bytes, _dartNativeMethods.length); + _registerDartMethods(contextId, bytes, _dartNativeMethods.length); } diff --git a/kraken/lib/src/bridge/native_types.dart b/kraken/lib/src/bridge/native_types.dart index 16078ab746..0f15045602 100644 --- a/kraken/lib/src/bridge/native_types.dart +++ b/kraken/lib/src/bridge/native_types.dart @@ -85,6 +85,7 @@ class RawNativeMessageEvent extends Struct { @Int64() external int length; } + // class RawNativeCustomEvent extends Struct { // Raw bytes represent the following fields. @@ -270,25 +271,21 @@ class NativeBoundingClientRect extends Struct { external double left; } +// using InvokeNativeBindingMethod = +// void (*)(NativeBindingObject* binding_object, NativeValue* return_value, NativeString* method, int32_t argc, NativeValue* argv); -typedef NativeDispatchEvent = Int32 Function( - Int32 contextId, - Pointer<NativeBindingObject> nativeBindingObject, - Pointer<NativeString> eventType, - Pointer<Void> nativeEvent, - Int32 isCustomEvent); -typedef NativeInvokeBindingMethod = Void Function( - Pointer<Void> nativePtr, - Pointer<NativeValue> returnValue, - Pointer<NativeString> method, - Int32 argc, - Pointer<NativeValue> argv); +typedef InvokeBindingsMethodsFromNative = Void Function(Pointer<NativeBindingObject> binding_object, + Pointer<NativeValue> return_value, Pointer<NativeString> method, Int32 argc, Pointer<NativeValue> argv); +typedef InvokeBindingMethodsFromDart = Void Function(Pointer<NativeBindingObject> binding_object, + Pointer<NativeValue> return_value, Pointer<NativeString> method, Int32 argc, Pointer<NativeValue> argv); +typedef DartInvokeBindingMethodsFromDart = void Function(Pointer<NativeBindingObject> binding_object, + Pointer<NativeValue> return_value, Pointer<NativeString> method, int argc, Pointer<NativeValue> argv); class NativeBindingObject extends Struct { external Pointer<Void> instance; - external Pointer<NativeFunction<NativeDispatchEvent>> dispatchEvent; + external Pointer<NativeFunction<InvokeBindingMethodsFromDart>> invokeBindingMethodFromDart; // Shared method called by JS side. - external Pointer<NativeFunction> invokeBindingMethod; + external Pointer<NativeFunction<InvokeBindingsMethodsFromNative>> invokeBindingMethodFromNative; } class NativeCanvasRenderingContext2D extends Struct { diff --git a/kraken/lib/src/bridge/native_value.dart b/kraken/lib/src/bridge/native_value.dart index 711b641a00..f55445542c 100644 --- a/kraken/lib/src/bridge/native_value.dart +++ b/kraken/lib/src/bridge/native_value.dart @@ -7,11 +7,9 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:kraken/bridge.dart'; +import 'package:kraken/foundation.dart'; class NativeValue extends Struct { - @Double() - external double float64; - @Int64() external int u; @@ -36,7 +34,7 @@ enum JSPointerType { NativeFunctionContext, NativeBoundingClientRect, NativeCanvasRenderingContext2D, - NativeBindingObject + BindingObject } typedef AnonymousNativeFunction = dynamic Function(List<dynamic> args); @@ -77,19 +75,9 @@ dynamic fromNativeValue(Pointer<NativeValue> nativeValue) { case JSValueType.TAG_NULL: return null; case JSValueType.TAG_FLOAT64: - return nativeValue.ref.float64; + return uInt64ToDouble(nativeValue.ref.u); case JSValueType.TAG_POINTER: - JSPointerType pointerType = JSPointerType.values[nativeValue.ref.float64.toInt()]; - switch (pointerType) { - case JSPointerType.NativeBoundingClientRect: - return Pointer.fromAddress(nativeValue.ref.u).cast<NativeBoundingClientRect>(); - case JSPointerType.NativeCanvasRenderingContext2D: - return Pointer.fromAddress(nativeValue.ref.u).cast<NativeCanvasRenderingContext2D>(); - case JSPointerType.NativeBindingObject: - return Pointer.fromAddress(nativeValue.ref.u).cast<NativeBindingObject>(); - default: - return Pointer.fromAddress(nativeValue.ref.u); - } + return Pointer.fromAddress(nativeValue.ref.u); case JSValueType.TAG_FUNCTION: case JSValueType.TAG_ASYNC_FUNCTION: break; @@ -112,20 +100,16 @@ void toNativeValue(Pointer<NativeValue> target, value) { target.ref.u = value ? 1 : 0; } else if (value is double) { target.ref.tag = JSValueType.TAG_FLOAT64.index; - target.ref.float64 = value; + target.ref.u = doubleToInt64(value); } else if (value is String) { target.ref.tag = JSValueType.TAG_STRING.index; target.ref.u = stringToNativeString(value).address; } else if (value is Pointer) { target.ref.tag = JSValueType.TAG_POINTER.index; target.ref.u = value.address; - if (value is Pointer<NativeBoundingClientRect>) { - target.ref.float64 = JSPointerType.NativeBoundingClientRect.index.toDouble(); - } else if (value is Pointer<NativeCanvasRenderingContext2D>) { - target.ref.float64 = JSPointerType.NativeCanvasRenderingContext2D.index.toDouble(); - } else if (value is Pointer<NativeBindingObject>) { - target.ref.float64 = JSPointerType.NativeBindingObject.index.toDouble(); - } + } else if (value is BindingObject) { + target.ref.tag = JSValueType.TAG_POINTER.index; + target.ref.u = (value.pointer as Pointer?)!.address; } else if (value is AsyncAnonymousNativeFunction) { int id = _functionId++; _asyncFunctionMap[id] = value; @@ -142,3 +126,14 @@ void toNativeValue(Pointer<NativeValue> target, value) { target.ref.u = str.toNativeUtf8().address; } } + +Pointer<NativeValue> makeNativeValueArguments(List<dynamic> args) { + Pointer<Pointer<NativeValue>> buffer = malloc.allocate(sizeOf<NativeValue>() * args.length).cast<Pointer<NativeValue>>(); + + for(int i = 0; i < args.length; i ++) { + buffer[i] = malloc.allocate(sizeOf<NativeValue>()); + toNativeValue(buffer[i], args[i]); + } + + return buffer.cast<NativeValue>(); +} diff --git a/kraken/lib/src/bridge/to_native.dart b/kraken/lib/src/bridge/to_native.dart index 7668dd1d01..60e68d0375 100644 --- a/kraken/lib/src/bridge/to_native.dart +++ b/kraken/lib/src/bridge/to_native.dart @@ -79,8 +79,7 @@ final DartInvokeEventListener _invokeModuleEvent = KrakenDynamicLibrary .lookup<NativeFunction<NativeInvokeEventListener>>('invokeModuleEvent') .asFunction(); -void invokeModuleEvent( - int contextId, String moduleName, Event? event, String extra) { +void invokeModuleEvent(int contextId, String moduleName, Event? event, String extra) { if (KrakenController.getControllerOfJSContextId(contextId) == null) { return; } @@ -96,30 +95,14 @@ void invokeModuleEvent( } typedef DartDispatchEvent = int Function( - int contextId, - Pointer<NativeBindingObject> nativeBindingObject, - Pointer<NativeString> eventType, - Pointer<Void> nativeEvent, - int isCustomEvent -); - -void emitUIEvent( - int contextId, Pointer<NativeBindingObject> nativeBindingObject, Event event) { - if (KrakenController.getControllerOfJSContextId(contextId) == null) { - return; - } - DartDispatchEvent dispatchEvent = nativeBindingObject.ref.dispatchEvent.asFunction(); - Pointer<Void> rawEvent = event.toRaw().cast<Void>(); - bool isCustomEvent = event is CustomEvent; - Pointer<NativeString> eventTypeString = stringToNativeString(event.type); - // @TODO: Make Event inherit BindingObject to pass value from bridge to dart. - int propagationStopped = dispatchEvent(contextId, nativeBindingObject, eventTypeString, rawEvent, isCustomEvent ? 1 : 0); - event.propagationStopped = propagationStopped == 1 ? true : false; - freeNativeString(eventTypeString); -} + int contextId, + Pointer<NativeBindingObject> nativeBindingObject, + Pointer<NativeString> eventType, + Pointer<Void> nativeEvent, + int isCustomEvent + ); -void emitModuleEvent( - int contextId, String moduleName, Event? event, String extra) { +void emitModuleEvent(int contextId, String moduleName, Event? event, String extra) { invokeModuleEvent(contextId, moduleName, event, extra); } @@ -156,6 +139,7 @@ final DartParseHTML _parseHTML = KrakenDynamicLibrary.ref .asFunction(); int _anonymousScriptEvaluationId = 0; + void evaluateScripts(int contextId, String code, {String? url, int line = 0}) { if (KrakenController.getControllerOfJSContextId(contextId) == null) { return; @@ -251,7 +235,7 @@ typedef DartRegisterPluginByteCode = void Function( final DartRegisterPluginByteCode _registerPluginByteCode = KrakenDynamicLibrary .ref .lookup<NativeFunction<NativeRegisterPluginByteCode>>( - 'registerPluginByteCode') + 'registerPluginByteCode') .asFunction(); void registerPluginByteCode(Uint8List bytecode, String name) { @@ -291,19 +275,6 @@ Future<void> reloadJSContext(int contextId) async { return completer.future; } -typedef NativeFlushUICommandCallback = Void Function(); -typedef DartFlushUICommandCallback = void Function(); - -final DartFlushUICommandCallback _flushUICommandCallback = KrakenDynamicLibrary - .ref - .lookup<NativeFunction<NativeFlushUICommandCallback>>( - 'flushUICommandCallback') - .asFunction(); - -void flushUICommandCallback() { - _flushUICommandCallback(); -} - typedef NativeDispatchUITask = Void Function( Int32 contextId, Pointer<Void> context, Pointer<Void> callback); typedef DartDispatchUITask = void Function( @@ -313,8 +284,7 @@ final DartDispatchUITask _dispatchUITask = KrakenDynamicLibrary.ref .lookup<NativeFunction<NativeDispatchUITask>>('dispatchUITask') .asFunction(); -void dispatchUITask( - int contextId, Pointer<Void> context, Pointer<Void> callback) { +void dispatchUITask(int contextId, Pointer<Void> context, Pointer<Void> callback) { _dispatchUITask(contextId, context, callback); } @@ -322,6 +292,8 @@ enum UICommandType { createElement, createTextNode, createComment, + createDocument, + createWindow, disposeEventTarget, addEvent, removeNode, @@ -407,8 +379,7 @@ final bool isEnabledLog = // 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<UICommand> readNativeUICommandToDart( - Pointer<Uint64> nativeCommandItems, int commandLength, int contextId) { +List<UICommand> readNativeUICommandToDart(Pointer<Uint64> nativeCommandItems, int commandLength, int contextId) { List<int> rawMemory = nativeCommandItems .cast<Int64>() .asTypedList(commandLength * nativeCommandSize) @@ -478,111 +449,119 @@ void clearUICommand(int contextId) { _clearUICommandItems(contextId); } -void flushUICommand() { - Map<int, KrakenController?> controllerMap = - KrakenController.getControllerMap(); - for (KrakenController? controller in controllerMap.values) { - if (controller == null) continue; - Pointer<Uint64> nativeCommandItems = - _getUICommandItems(controller.view.contextId); - int commandLength = _getUICommandItemSize(controller.view.contextId); - - if (commandLength == 0 || nativeCommandItems == nullptr) { - continue; - } +void flushUICommandWithContextId(int contextId) { + KrakenController? controller = KrakenController.getControllerOfJSContextId(contextId); + if (controller != null) { + flushUICommand(controller.view); + } +} - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_START); - } +void flushUICommand(KrakenViewController view) { + Pointer<Uint64> nativeCommandItems = + _getUICommandItems(view.contextId); + int commandLength = _getUICommandItemSize(view.contextId); - List<UICommand> commands = readNativeUICommandToDart( - nativeCommandItems, commandLength, controller.view.contextId); + if (commandLength == 0 || nativeCommandItems == nullptr) { + return; + } - SchedulerBinding.instance!.scheduleFrame(); + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_START); + } - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_END); - } + List<UICommand> commands = readNativeUICommandToDart( + nativeCommandItems, commandLength, view.contextId); + + SchedulerBinding.instance!.scheduleFrame(); + + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_END); + } - Map<int, bool> pendingStylePropertiesTargets = {}; - - // For new ui commands, we needs to tell engine to update frames. - for (int i = 0; i < commandLength; i++) { - UICommand command = commands[i]; - UICommandType commandType = command.type; - int id = command.id; - Pointer nativePtr = command.nativePtr; - - try { - switch (commandType) { - case UICommandType.createElement: - controller.view.createElement( - id, nativePtr.cast<NativeBindingObject>(), command.args[0]); - break; - case UICommandType.createTextNode: - controller.view.createTextNode( - id, nativePtr.cast<NativeBindingObject>(), command.args[0]); - break; - case UICommandType.createComment: - controller.view - .createComment(id, nativePtr.cast<NativeBindingObject>()); - break; - case UICommandType.disposeEventTarget: - controller.view.disposeEventTarget(id); - break; - case UICommandType.addEvent: - controller.view.addEvent(id, command.args[0]); - break; - case UICommandType.removeEvent: - controller.view.removeEvent(id, command.args[0]); - break; - case UICommandType.insertAdjacentNode: - int childId = int.parse(command.args[0]); - String position = command.args[1]; - controller.view.insertAdjacentNode(id, position, childId); - break; - case UICommandType.removeNode: - controller.view.removeNode(id); - break; - case UICommandType.cloneNode: - int newId = int.parse(command.args[0]); - controller.view.cloneNode(id, newId); - break; - case UICommandType.setStyle: - String key = command.args[0]; - String value = command.args[1]; - controller.view.setInlineStyle(id, key, value); - pendingStylePropertiesTargets[id] = true; - break; - case UICommandType.setAttribute: - String key = command.args[0]; - String value = command.args[1]; - controller.view.setAttribute(id, key, value); - break; - case UICommandType.removeAttribute: - String key = command.args[0]; - controller.view.removeAttribute(id, key); - break; - case UICommandType.createDocumentFragment: - controller.view.createDocumentFragment( - id, nativePtr.cast<NativeBindingObject>()); - break; - default: - break; - } - } catch (e, stack) { - print('$e\n$stack'); + Map<int, bool> pendingStylePropertiesTargets = {}; + + // For new ui commands, we needs to tell engine to update frames. + for (int i = 0; i < commandLength; i++) { + UICommand command = commands[i]; + UICommandType commandType = command.type; + int id = command.id; + Pointer nativePtr = command.nativePtr; + + try { + switch (commandType) { + case UICommandType.createElement: + view.createElement( + id, nativePtr.cast<NativeBindingObject>(), command.args[0]); + break; + case UICommandType.createDocument: + view.initDocument(id, nativePtr.cast<NativeBindingObject>()); + break; + case UICommandType.createWindow: + view.initWindow(id, nativePtr.cast<NativeBindingObject>()); + break; + case UICommandType.createTextNode: + view.createTextNode( + id, nativePtr.cast<NativeBindingObject>(), command.args[0]); + break; + case UICommandType.createComment: + view + .createComment(id, nativePtr.cast<NativeBindingObject>()); + break; + case UICommandType.disposeEventTarget: + view.disposeEventTarget(id); + break; + case UICommandType.addEvent: + view.addEvent(id, command.args[0]); + break; + case UICommandType.removeEvent: + view.removeEvent(id, command.args[0]); + break; + case UICommandType.insertAdjacentNode: + int childId = int.parse(command.args[0]); + String position = command.args[1]; + view.insertAdjacentNode(id, position, childId); + break; + case UICommandType.removeNode: + view.removeNode(id); + break; + case UICommandType.cloneNode: + int newId = int.parse(command.args[0]); + view.cloneNode(id, newId); + break; + case UICommandType.setStyle: + String key = command.args[0]; + String value = command.args[1]; + view.setInlineStyle(id, key, value); + pendingStylePropertiesTargets[id] = true; + break; + case UICommandType.setAttribute: + String key = command.args[0]; + String value = command.args[1]; + view.setAttribute(id, key, value); + break; + case UICommandType.removeAttribute: + String key = command.args[0]; + view.removeAttribute(id, key); + break; + case UICommandType.createDocumentFragment: + view.createDocumentFragment( + id, nativePtr.cast<NativeBindingObject>()); + break; + default: + break; } + } catch (e, stack) { + print('$e\n$stack'); } + } - // For pending style properties, we needs to flush to render style. - for (int id in pendingStylePropertiesTargets.keys) { - try { - controller.view.flushPendingStyleProperties(id); - } catch (e, stack) { - print('$e\n$stack'); - } + // For pending style properties, we needs to flush to render style. + for (int id in pendingStylePropertiesTargets.keys) { + try { + view.flushPendingStyleProperties(id); + } catch (e, stack) { + print('$e\n$stack'); } - pendingStylePropertiesTargets.clear(); } + pendingStylePropertiesTargets.clear(); } diff --git a/kraken/lib/src/devtools/server.dart b/kraken/lib/src/devtools/server.dart index e43d13f581..9459697e92 100644 --- a/kraken/lib/src/devtools/server.dart +++ b/kraken/lib/src/devtools/server.dart @@ -96,7 +96,7 @@ void initInspectorServerNativeBinding(int contextId) { Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); - _registerInspectorServerDartMethods(bytes, _dartNativeMethods.length); + _registerInspectorServerDartMethods(contextId, bytes, _dartNativeMethods.length); } void serverIsolateEntryPoint(SendPort isolateToMainStream) { diff --git a/kraken/lib/src/devtools/service.dart b/kraken/lib/src/devtools/service.dart index ea6255a114..ee206dbfc3 100644 --- a/kraken/lib/src/devtools/service.dart +++ b/kraken/lib/src/devtools/service.dart @@ -115,12 +115,12 @@ class ChromeDevToolsService extends DevToolsService { // @TODO: Implement and remove. // ignore: unused_element - static bool _registerUIDartMethodsToCpp() { + static bool _registerUIDartMethodsToCpp(int contextId) { final DartRegisterDartMethods _registerDartMethods = KrakenDynamicLibrary.ref.lookup<NativeFunction<NativeRegisterDartMethods>>('registerUIDartMethods').asFunction(); Pointer<Uint64> bytes = malloc.allocate<Uint64>(_dartNativeMethods.length * sizeOf<Uint64>()); Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); - _registerDartMethods(bytes, _dartNativeMethods.length); + _registerDartMethods(contextId, bytes, _dartNativeMethods.length); return true; } } diff --git a/kraken/lib/src/dom/event_target.dart b/kraken/lib/src/dom/event_target.dart index 92563ab913..ba9da41f60 100644 --- a/kraken/lib/src/dom/event_target.dart +++ b/kraken/lib/src/dom/event_target.dart @@ -22,6 +22,7 @@ abstract class EventTarget extends BindingObject { @protected bool hasEventListener(String type) => _eventHandlers.containsKey(type); + // TODO: Support addEventListener options: capture, once, passive, signal. @mustCallSuper void addEventListener(String eventType, EventHandler eventHandler) { if (_disposed) return; diff --git a/kraken/lib/src/dom/window.dart b/kraken/lib/src/dom/window.dart index a89deae991..11d79a38a8 100644 --- a/kraken/lib/src/dom/window.dart +++ b/kraken/lib/src/dom/window.dart @@ -2,6 +2,8 @@ * Copyright (C) 2019-present The Kraken authors. All rights reserved. */ import 'dart:ui'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; import 'package:kraken/bridge.dart'; import 'package:kraken/dom.dart'; @@ -16,7 +18,7 @@ class Window extends EventTarget { final Screen screen; Window(BindingContext? context, this.document) - : screen = Screen(context), super(context); + : screen = Screen(BindingContext(context!.contextId, malloc.allocate(sizeOf<NativeBindingObject>()))), super(context); @override EventTarget? get parentEventTarget => null; @@ -101,8 +103,8 @@ 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 (event.type == EVENT_DOM_CONTENT_LOADED || event.type == EVENT_LOAD || event.type == EVENT_ERROR) { - flushUICommand(); + 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/kraken/lib/src/foundation/binding.dart b/kraken/lib/src/foundation/binding.dart index bf24ced53a..3cefeba352 100644 --- a/kraken/lib/src/foundation/binding.dart +++ b/kraken/lib/src/foundation/binding.dart @@ -2,12 +2,14 @@ * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ import 'package:flutter/foundation.dart'; +import 'package:kraken/bridge.dart'; +import 'dart:ffi'; typedef BindingObjectOperation = void Function(BindingObject bindingObject); class BindingContext { final int contextId; - final pointer; + final Pointer<NativeBindingObject> pointer; const BindingContext(this.contextId, this.pointer); } diff --git a/kraken/lib/src/launcher/controller.dart b/kraken/lib/src/launcher/controller.dart index b52c170a6a..a65f033c86 100644 --- a/kraken/lib/src/launcher/controller.dart +++ b/kraken/lib/src/launcher/controller.dart @@ -25,9 +25,6 @@ import 'package:kraken/module.dart'; import 'package:kraken/rendering.dart'; import 'package:kraken/widget.dart'; -const int WINDOW_ID = -1; -const int DOCUMENT_ID = -2; - // Error handler when load bundle failed. typedef LoadHandler = void Function(KrakenController controller); typedef LoadErrorHandler = void Function(FlutterError error, StackTrace stack); @@ -66,8 +63,6 @@ abstract class DevToolsService { // An kraken View Controller designed for multiple kraken view control. class KrakenViewController implements WidgetsBindingObserver, ElementsBindingObserver { - static Map<int, Pointer<NativeBindingObject>> documentNativePtrMap = {}; - static Map<int, Pointer<NativeBindingObject>> windowNativePtrMap = {}; KrakenController rootController; @@ -121,7 +116,7 @@ class KrakenViewController PerformanceTiming.instance().mark(PERF_BRIDGE_INIT_START); } BindingBridge.setup(); - _contextId = contextId ?? initBridge(); + _contextId = contextId ?? initBridge(this); if (kProfileMode) { PerformanceTiming.instance().mark(PERF_BRIDGE_INIT_END); @@ -149,20 +144,39 @@ class KrakenViewController defineBuiltInElements(); + // Execute UICommand.createDocument and UICommand.createWindow to initialize window and document. + flushUICommand(this); + + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_ELEMENT_MANAGER_INIT_END); + } + } + + // Index value which identify javascript runtime context. + late int _contextId; + int get contextId => _contextId; + + // Enable print debug message when rendering. + bool enableDebug; + + // Kraken have already disposed. + bool _disposed = false; + + bool get disposed => _disposed; + + late RenderViewportBox viewport; + late Document document; + late Window window; + + void initDocument(int targetId, Pointer<NativeBindingObject> pointer) { document = Document( - BindingContext(_contextId, documentNativePtrMap[_contextId]!), + BindingContext(_contextId, pointer), viewport: viewport, controller: rootController, gestureListener: gestureListener, widgetDelegate: widgetDelegate, ); - _setEventTarget(DOCUMENT_ID, document); - - window = Window( - BindingContext(_contextId, windowNativePtrMap[_contextId]!), - document); - _registerPlatformBrightnessChange(); - _setEventTarget(WINDOW_ID, window); + _setEventTarget(targetId, document); // Listeners need to be registered to window in order to dispatch events on demand. if (gestureListener != null) { @@ -183,6 +197,14 @@ class KrakenViewController document.addEventListener(EVENT_DRAG, (Event event) => listener.onDrag!(event as GestureEvent)); } } + } + + void initWindow(int targetId, Pointer<NativeBindingObject> pointer) { + window = Window( + BindingContext(_contextId, pointer), + document); + _registerPlatformBrightnessChange(); + _setEventTarget(targetId, window); // Blur input element when new input focused. window.addEventListener(EVENT_CLICK, (event) { @@ -194,28 +216,8 @@ class KrakenViewController (event.target as Element).focus(); } }); - - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_ELEMENT_MANAGER_INIT_END); - } } - // Index value which identify javascript runtime context. - late int _contextId; - int get contextId => _contextId; - - // Enable print debug message when rendering. - bool enableDebug; - - // Kraken have already disposed. - bool _disposed = false; - - bool get disposed => _disposed; - - late RenderViewportBox viewport; - late Document document; - late Window window; - void evaluateJavaScripts(String code) { assert(!_disposed, 'Kraken have already disposed'); evaluateScripts(_contextId, code); diff --git a/kraken/lib/src/module/timer.dart b/kraken/lib/src/module/timer.dart index 3e9a5d598e..e33693684b 100644 --- a/kraken/lib/src/module/timer.dart +++ b/kraken/lib/src/module/timer.dart @@ -62,7 +62,9 @@ mixin TimerMixin { int setTimeout(int timeout, void Function() callback) { Duration timeoutDurationMS = Duration(milliseconds: timeout); int id = _timerId++; + print('set timer: $id'); _timerMap[id] = Timer(timeoutDurationMS, () { + print('trigger timer: $id'); callback(); _timerMap.remove(id); }); @@ -72,6 +74,7 @@ mixin TimerMixin { void clearTimeout(int timerId) { // If timer already executed, which will be removed. if (_timerMap[timerId] != null) { + print('clear timer $timerId'); _timerMap[timerId]!.cancel(); _timerMap.remove(timerId); } diff --git a/scripts/tasks.js b/scripts/tasks.js index 21706f1c16..ce50ce93d8 100644 --- a/scripts/tasks.js +++ b/scripts/tasks.js @@ -149,33 +149,6 @@ task('clean', () => { } }); -const libOutputPath = join(TARGET_PATH, platform, 'lib'); - -function findDebugJSEngine(platform) { - if (platform == 'macos' || platform == 'ios') { - let packageConfigFilePath = path.join(paths.kraken, '.dart_tool/package_config.json'); - - if (!fs.existsSync(packageConfigFilePath)) { - execSync('flutter pub get', { - cwd: paths.kraken, - stdio: 'inherit' - }); - } - - let packageConfig = require(packageConfigFilePath); - let packages = packageConfig.packages; - - let jscPackageInfo = packages.find((i) => i.name === 'jsc'); - if (!jscPackageInfo) { - throw new Error('Can not locate `jsc` dart package, please add jsc deps before build kraken libs.'); - } - - let rootUri = jscPackageInfo.rootUri; - let jscPackageLocation = path.join(paths.kraken, '.dart_tool', rootUri); - return path.join(jscPackageLocation, platform, 'JavaScriptCore.framework'); - } -} - task('build-darwin-kraken-lib', done => { let externCmakeArgs = []; let buildType = 'Debug'; @@ -221,9 +194,6 @@ task('build-darwin-kraken-lib', done => { const binaryPath = path.join(paths.bridge, `build/macos/lib/x86_64/libkraken.dylib`); - if (targetJSEngine === 'jsc') { - execSync(`install_name_tool -change /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/JavaScriptCore @rpath/JavaScriptCore.framework/Versions/A/JavaScriptCore ${binaryPath}`); - } if (buildMode == 'Release' || buildMode == 'RelWithDebInfo') { execSync(`dsymutil ${binaryPath}`, { stdio: 'inherit' }); execSync(`strip -S -X -x ${binaryPath}`, { stdio: 'inherit' }); @@ -809,7 +779,7 @@ task('run-benchmark', async (done) => { const err = new Error('The IP address was not found.'); done(err); } - + let androidDevices = getDevicesInfo(); let performanceInfos = execSync( @@ -829,7 +799,7 @@ task('run-benchmark', async (done) => { let performanceDatas = JSON.parse(match[0]); // Remove the top and the bottom five from the final numbers to eliminate fluctuations, and calculate the average. performanceDatas = performanceDatas.sort().slice(5, performanceDatas.length - 5); - + // Save performance list to file and upload to OSS. const listFile = path.join(__dirname, `${viewType}-load-time-list.js`); fs.writeFileSync(listFile, `performanceCallback('${viewType}LoadtimeList', [${performanceDatas.toString()}]);`); @@ -849,8 +819,8 @@ task('run-benchmark', async (done) => { } } } - + execSync('adb uninstall com.example.performance_tests'); - + done(); });