diff --git a/.github/workflows/example_build.yml b/.github/workflows/example_build.yml index 625de0835a..8c1e6455e3 100644 --- a/.github/workflows/example_build.yml +++ b/.github/workflows/example_build.yml @@ -10,7 +10,7 @@ on: env: nodeVersion: "16" cmakeVersion: "3.22.x" - flutterVersion: "3.0.4" + flutterVersion: "3.0.5" jobs: build_android-app_in_macos: diff --git a/.github/workflows/integration_test_flutter.yml b/.github/workflows/integration_test_flutter.yml index 652781f7b4..0cf2649772 100644 --- a/.github/workflows/integration_test_flutter.yml +++ b/.github/workflows/integration_test_flutter.yml @@ -5,7 +5,7 @@ on: [workflow_dispatch, pull_request] env: nodeVersion: "16" cmakeVersion: "3.22.x" - flutterVersion: "3.0.4" + flutterVersion: "3.0.5" # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: @@ -30,7 +30,7 @@ jobs: with: cmake-version: ${{ env.cmakeVersion }} - run: npm i - - run: ENABLE_ASAN=true npm run build:bridge:macos + - run: npm run build:bridge:macos - uses: actions/upload-artifact@v2 with: name: macos_bridge_binary 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 9b7c1b8836..c2c0e97ee0 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -28,15 +28,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 @@ -64,10 +59,15 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") endif() if (ENABLE_ASAN) - add_compile_options(-fsanitize=address -fno-omit-frame-pointer -O1) + add_compile_options(-fsanitize=address -fno-omit-frame-pointer) add_link_options(-fsanitize=address -fno-omit-frame-pointer) endif () +if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") + # Avoid quickjs stackoverflow. + add_compile_options(-O1) +endif() + if (DEFINED PLATFORM) if (${PLATFORM} STREQUAL "OS") add_compile_options(-fno-aligned-allocation) @@ -75,28 +75,15 @@ if (DEFINED PLATFORM) endif() list(APPEND BRIDGE_SOURCE - webf_bridge.cc - ${CMAKE_CURRENT_SOURCE_DIR}/include/webf_bridge.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/webf_foundation.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/dart_methods.h + webf_bridge.cc foundation/logging.cc - foundation/logging.h - foundation/colors.h - foundation/ref_counted_internal.h - foundation/ref_counter.h - foundation/ref_ptr.h - foundation/ref_ptr_internal.h - foundation/ui_task_queue.h + foundation/native_string.cc foundation/ui_task_queue.cc - foundation/inspector_task_queue.h foundation/inspector_task_queue.cc foundation/task_queue.cc - foundation/task_queue.h + foundation/string_view.cc + foundation/native_value.cc 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 ) @@ -117,6 +104,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 +174,229 @@ if ($ENV{WEBF_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 - 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.c - bindings/qjs/qjs_patch.h + # Binding files + bindings/qjs/dictionary_base.cc + bindings/qjs/js_based_event_listener.cc + bindings/qjs/js_event_handler.cc + bindings/qjs/js_event_listener.cc + bindings/qjs/binding_initializer.cc + bindings/qjs/member_installer.cc + bindings/qjs/source_location.cc + bindings/qjs/cppgc/gc_visitor.cc + bindings/qjs/cppgc/mutation_scope.cc + bindings/qjs/script_wrappable.cc + bindings/qjs/native_string_utils.cc + bindings/qjs/qjs_engine_patch.cc + bindings/qjs/qjs_function.cc + bindings/qjs/script_value.cc + bindings/qjs/script_promise.cc + bindings/qjs/script_promise_resolver.cc + bindings/qjs/atomic_string.cc + bindings/qjs/exception_state.cc + bindings/qjs/exception_message.cc bindings/qjs/rejected_promises.cc - 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 + # Core sources + core/executing_context.cc + core/script_state.cc + core/page.cc + core/dart_methods.cc + core/executing_context_data.cc + core/fileapi/blob.cc + core/fileapi/blob_part.cc + core/fileapi/blob_property_bag.cc + core/frame/console.cc + core/frame/dom_timer.cc + core/frame/dom_timer_coordinator.cc + core/frame/window_or_worker_global_scope.cc + core/frame/module_listener.cc + core/frame/module_listener_container.cc + core/frame/module_manager.cc + core/frame/module_callback.cc + core/frame/module_context_coordinator.cc + core/frame/window.cc + core/frame/screen.cc + core/frame/legacy/location.cc + core/timing/performance.cc + core/timing/performance_mark.cc + core/timing/performance_entry.cc + core/timing/performance_measure.cc + core/css/legacy/css_style_declaration.cc + core/dom/frame_request_callback_collection.cc + core/dom/events/registered_eventListener.cc + core/dom/events/event_listener_map.cc + core/dom/events/event.cc + core/dom/events/custom_event.cc + core/dom/events/event_target.cc + core/dom/events/event_listener_map.cc + core/dom/events/event_target_impl.cc + core/binding_object.cc + core/dom/node.cc + core/dom/node_traversal.cc + core/dom/live_node_list_base.cc + core/dom/character_data.cc + core/dom/comment.cc + core/dom/text.cc + core/dom/tree_scope.cc + core/dom/element.cc + core/dom/parent_node.cc + core/dom/element_data.cc + core/dom/document.cc + core/dom/scripted_animation_controller.cc + core/dom/node_data.cc + core/dom/document_fragment.cc + core/dom/child_node_list.cc + core/dom/empty_node_list.cc + core/dom/container_node.cc + core/html/custom/widget_element.cc + core/events/error_event.cc + core/events/message_event.cc + core/events/animation_event.cc + core/events/close_event.cc + core/events/ui_event.cc + core/events/focus_event.cc + core/events/gesture_event.cc + core/events/input_event.cc + core/events/touch_event.cc + core/events/mouse_event.cc + core/events/pop_state_event.cc + core/events/pointer_event.cc + core/events/transition_event.cc + core/events/intersection_change_event.cc + core/events/keyboard_event.cc + core/events/promise_rejection_event.cc + core/html/parser/html_parser.cc + core/html/legacy/html_collection.cc + core/html/html_element.cc + core/html/html_div_element.cc + core/html/html_head_element.cc + core/html/html_body_element.cc + core/html/html_html_element.cc + core/html/html_template_element.cc + core/html/html_all_collection.cc + core/html/html_anchor_element.cc + core/html/html_image_element.cc + core/html/html_script_element.cc + core/html/html_link_element.cc + core/html/html_unknown_element.cc + core/html/image.cc + core/html/canvas/html_canvas_element.cc + core/html/canvas/canvas_rendering_context.cc + core/html/canvas/canvas_rendering_context_2d.cc + core/html/forms/html_button_element.cc + core/html/forms/html_input_element.cc + core/html/forms/html_textarea_element.cc + # Legacy implements, should remove them in the future. + core/dom/legacy/space_split_string.cc + core/dom/legacy/element_attributes.cc + core/dom/legacy/bounding_client_rect.cc + core/input/touch.cc + core/input/touch_list.cc + ) + + # Gen sources. + list(APPEND BRIDGE_SOURCE + out/names_installer.cc + out/qjs_console.cc + out/qjs_module_manager.cc + out/qjs_window_or_worker_global_scope.cc + out/qjs_window.cc + out/qjs_location.cc + out/qjs_blob.cc + out/qjs_event.cc + out/qjs_add_event_listener_options.cc + out/qjs_event_listener_options.cc + out/qjs_error_event.cc + out/qjs_message_event.cc + out/qjs_message_event_init.cc + out/qjs_close_event.cc + out/qjs_close_event_init.cc + out/qjs_focus_event.cc + out/qjs_focus_event_init.cc + out/qjs_input_event.cc + out/qjs_input_event_init.cc + out/qjs_pop_state_event.cc + out/qjs_pop_state_event_init.cc + out/qjs_ui_event.cc + out/qjs_ui_event_init.cc + out/qjs_gesture_event.cc + out/qjs_gesture_event_init.cc + out/qjs_intersection_change_event.cc + out/qjs_intersection_change_event_init.cc + out/qjs_touch.cc + out/qjs_touch_init.cc + out/qjs_touch_list.cc + out/qjs_touch_event.cc + out/qjs_touch_event_init.cc + out/qjs_pointer_event.cc + out/qjs_pointer_event_init.cc + out/qjs_mouse_event.cc + out/qjs_mouse_event_init.cc + out/qjs_transition_event.cc + out/qjs_transition_event_init.cc + out/event_factory.cc + out/qjs_custom_event.cc + out/qjs_custom_event_init.cc + out/qjs_keyboard_event.cc + out/qjs_keyboard_event_init.cc + out/qjs_animation_event.cc + out/qjs_animation_event_init.cc + out/qjs_error_event_init.cc + out/qjs_event_init.cc + out/qjs_event_target.cc + out/qjs_node.cc + out/qjs_document.cc + out/qjs_element.cc + out/qjs_element_attributes.cc + out/qjs_character_data.cc + out/qjs_comment.cc + out/qjs_document_fragment.cc + out/qjs_bounding_client_rect.cc + out/qjs_css_style_declaration.cc + out/qjs_text.cc + out/qjs_screen.cc + out/qjs_node_list.cc + out/event_type_names.cc + out/built_in_string.cc + out/binding_call_methods.cc + out/qjs_scroll_options.cc + out/qjs_scroll_to_options.cc + out/qjs_html_element.cc + out/qjs_html_all_collection.cc + out/qjs_html_anchor_element.cc + out/qjs_html_div_element.cc + out/qjs_html_head_element.cc + out/qjs_html_body_element.cc + out/qjs_html_html_element.cc + out/qjs_html_image_element.cc + out/qjs_html_canvas_element.cc + out/qjs_html_link_element.cc + out/qjs_image.cc + out/qjs_widget_element.cc + out/qjs_canvas_rendering_context_2d.cc + out/qjs_canvas_rendering_context.cc + out/canvas_types.cc + out/qjs_html_button_element.cc + out/qjs_html_input_element.cc + out/qjs_html_textarea_element.cc + out/qjs_html_script_element.cc + out/qjs_promise_rejection_event.cc + out/qjs_promise_rejection_event_init.cc + out/qjs_html_template_element.cc + out/qjs_html_unknown_element.cc + out/qjs_performance.cc + out/qjs_performance_entry.cc + out/qjs_performance_mark.cc + out/qjs_performance_measure.cc + out/performance_entry_names.cc + out/qjs_performance_measure_options.cc + out/qjs_performance_mark_options.cc + out/performance_mark_constants.cc + out/html_element_factory.cc + out/html_names.cc + out/script_type_names.cc + out/defined_properties.cc + out/defined_properties_initializer.cc + out/element_attribute_names.cc ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 @@ -297,7 +408,7 @@ endif () list(APPEND PUBLIC_HEADER include/webf_bridge.h - ) +) add_library(webf SHARED ${BRIDGE_SOURCE}) add_library(webf_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..9dd0a882f2 --- /dev/null +++ b/bridge/bindings/qjs/atomic_string.cc @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "atomic_string.h" +#include "built_in_string.h" + +namespace webf { + +AtomicString AtomicString::Empty() { + return built_in_string::kempty_string; +} + +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)); +} + +AtomicString::StringKind GetStringKind(const NativeString* native_string) { + if (!native_string->length()) { + return AtomicString::StringKind::kIsMixed; + } + + AtomicString::StringKind predictKind = std::islower(native_string->string()[0]) + ? AtomicString::StringKind::kIsLowerCase + : AtomicString::StringKind::kIsUpperCase; + for (int i = 0; i < native_string->length(); i++) { + uint16_t c = native_string->string()[i]; + if (predictKind == AtomicString::StringKind::kIsUpperCase && !std::isupper(c)) { + return AtomicString::StringKind::kIsMixed; + } else if (predictKind == AtomicString::StringKind::kIsLowerCase && !std::islower(c)) { + return AtomicString::StringKind::kIsMixed; + } + } + + return predictKind; +} + +} // 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, const NativeString* native_string) + : runtime_(JS_GetRuntime(ctx)), + ctx_(ctx), + atom_(JS_NewUnicodeAtom(ctx, native_string->string(), native_string->length())), + kind_(GetStringKind(native_string)), + length_(native_string->length()) {} + +AtomicString::AtomicString(JSContext* ctx, JSValue value) + : runtime_(JS_GetRuntime(ctx)), + ctx_(ctx), + atom_(JS_IsNull(value) ? built_in_string::kempty_string.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::IsEmpty() const { + return *this == built_in_string::kempty_string; +} + +std::string AtomicString::ToStdString() const { + if (IsEmpty()) + return ""; + + 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) { + JS_FreeAtom(other.ctx_, atom_); + 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_empty_string) + 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_empty_string) + 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 webf diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h new file mode 100644 index 0000000000..fa755d0ecf --- /dev/null +++ b/bridge/bindings/qjs/atomic_string.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ +#define BRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ + +#include +#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 webf { + +// 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 { + WEBF_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(); + static AtomicString From(JSContext* ctx, NativeString* native_string); + + AtomicString() = default; + AtomicString(JSContext* ctx, const std::string& string); + AtomicString(JSContext* ctx, const NativeString* native_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 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_empty_string}; + mutable JSAtom atom_upper_{JS_ATOM_empty_string}; + mutable JSAtom atom_lower_{JS_ATOM_empty_string}; + StringKind kind_; +}; + +} // namespace webf + +#endif // BRIDGE_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..e221753a36 --- /dev/null +++ b/bridge/bindings/qjs/atomic_string_test.cc @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF 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 webf; + +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(); + 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()}; + 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(); + 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..ffa9d6de20 --- /dev/null +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "binding_initializer.h" +#include "core/executing_context.h" + +#include "qjs_animation_event.h" +#include "qjs_blob.h" +#include "qjs_bounding_client_rect.h" +#include "qjs_canvas_rendering_context.h" +#include "qjs_canvas_rendering_context_2d.h" +#include "qjs_character_data.h" +#include "qjs_close_event.h" +#include "qjs_comment.h" +#include "qjs_console.h" +#include "qjs_css_style_declaration.h" +#include "qjs_custom_event.h" +#include "qjs_document.h" +#include "qjs_document_fragment.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_focus_event.h" +#include "qjs_gesture_event.h" +#include "qjs_html_all_collection.h" +#include "qjs_html_anchor_element.h" +#include "qjs_html_body_element.h" +#include "qjs_html_button_element.h" +#include "qjs_html_canvas_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_image_element.h" +#include "qjs_html_input_element.h" +#include "qjs_html_link_element.h" +#include "qjs_html_script_element.h" +#include "qjs_html_template_element.h" +#include "qjs_html_textarea_element.h" +#include "qjs_html_unknown_element.h" +#include "qjs_image.h" +#include "qjs_input_event.h" +#include "qjs_intersection_change_event.h" +#include "qjs_keyboard_event.h" +#include "qjs_location.h" +#include "qjs_message_event.h" +#include "qjs_module_manager.h" +#include "qjs_mouse_event.h" +#include "qjs_node.h" +#include "qjs_node_list.h" +#include "qjs_performance.h" +#include "qjs_performance_entry.h" +#include "qjs_performance_mark.h" +#include "qjs_performance_measure.h" +#include "qjs_pointer_event.h" +#include "qjs_pop_state_event.h" +#include "qjs_promise_rejection_event.h" +#include "qjs_screen.h" +#include "qjs_text.h" +#include "qjs_touch.h" +#include "qjs_touch_event.h" +#include "qjs_touch_list.h" +#include "qjs_transition_event.h" +#include "qjs_ui_event.h" +#include "qjs_widget_element.h" +#include "qjs_window.h" +#include "qjs_window_or_worker_global_scope.h" + +namespace webf { + +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); + QJSUIEvent::Install(context); + QJSErrorEvent::Install(context); + QJSPromiseRejectionEvent::Install(context); + QJSMessageEvent::Install(context); + QJSAnimationEvent::Install(context); + QJSCloseEvent::Install(context); + QJSFocusEvent::Install(context); + QJSGestureEvent::Install(context); + QJSInputEvent::Install(context); + QJSCustomEvent::Install(context); + QJSMouseEvent::Install(context); + QJSPointerEvent::Install(context); + QJSTouchEvent::Install(context); + QJSPopStateEvent::Install(context); + QJSTransitionEvent::Install(context); + QJSIntersectionChangeEvent::Install(context); + QJSKeyboardEvent::Install(context); + QJSNode::Install(context); + QJSNodeList::Install(context); + QJSDocument::Install(context); + QJSDocumentFragment::Install(context); + QJSCharacterData::Install(context); + QJSText::Install(context); + QJSComment::Install(context); + QJSElement::Install(context); + QJSHTMLElement::Install(context); + QJSWidgetElement::Install(context); + QJSHTMLDivElement::Install(context); + QJSHTMLHeadElement::Install(context); + QJSHTMLBodyElement::Install(context); + QJSHTMLHtmlElement::Install(context); + QJSHTMLAnchorElement::Install(context); + QJSHTMLImageElement::Install(context); + QJSHTMLInputElement::Install(context); + QJSHTMLTextareaElement::Install(context); + QJSHTMLButtonElement::Install(context); + QJSImage::Install(context); + QJSHTMLScriptElement::Install(context); + QJSHTMLLinkElement::Install(context); + QJSHTMLUnknownElement::Install(context); + QJSHTMLTemplateElement::Install(context); + QJSHTMLCanvasElement::Install(context); + QJSCanvasRenderingContext::Install(context); + QJSCanvasRenderingContext2D::Install(context); + QJSCSSStyleDeclaration::Install(context); + QJSBoundingClientRect::Install(context); + QJSHTMLAllCollection::Install(context); + QJSScreen::Install(context); + QJSBlob::Install(context); + QJSTouch::Install(context); + QJSTouchList::Install(context); + QJSPerformance::Install(context); + QJSPerformanceEntry::Install(context); + QJSPerformanceMark::Install(context); + QJSPerformanceMeasure::Install(context); + + // Legacy bindings, not standard. + QJSElementAttributes::Install(context); +} + +} // namespace webf diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h new file mode 100644 index 0000000000..9510ce44a5 --- /dev/null +++ b/bridge/bindings/qjs/binding_initializer.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDING_INITIALIZER_H +#define BRIDGE_BINDING_INITIALIZER_H + +#include + +namespace webf { + +class ExecutingContext; + +void InstallBindings(ExecutingContext* context); + +} // namespace webf + +#endif // BRIDGE_BINDING_INITIALIZER_H diff --git a/bridge/bindings/qjs/bom/blob.cc b/bridge/bindings/qjs/bom/blob.cc deleted file mode 100644 index 0ce5f76329..0000000000 --- a/bridge/bindings/qjs/bom/blob.cc +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "blob.h" -#include "dart_methods.h" - -namespace webf::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 webf::binding::qjs diff --git a/bridge/bindings/qjs/bom/blob.h b/bridge/bindings/qjs/bom/blob.h deleted file mode 100644 index 8387d8021b..0000000000 --- a/bridge/bindings/qjs/bom/blob.h +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_BLOB_H -#define BRIDGE_BLOB_H - -#include "bindings/qjs/host_class.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_BLOB_H diff --git a/bridge/bindings/qjs/bom/console.cc b/bridge/bindings/qjs/bom/console.cc deleted file mode 100644 index 35c13fab18..0000000000 --- a/bridge/bindings/qjs/bom/console.cc +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "console.h" -#include "foundation/logging.h" - -namespace webf::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, "__webf_print__", 2); -} - -} // namespace webf::binding::qjs diff --git a/bridge/bindings/qjs/bom/console.h b/bridge/bindings/qjs/bom/console.h deleted file mode 100644 index 60cc7feff7..0000000000 --- a/bridge/bindings/qjs/bom/console.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_CONSOLE_H -#define BRIDGE_CONSOLE_H - -#include "bindings/qjs/executing_context.h" - -namespace webf::binding::qjs { - -void bindConsole(ExecutionContext* context); - -} - -#endif // BRIDGE_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 e3a9e2a8d3..0000000000 --- a/bridge/bindings/qjs/bom/dom_timer_coordinator.cc +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "dom_timer_coordinator.h" -#include "dart_methods.h" -#include "timer.h" - -#if UNIT_TEST -#include "webf_test_env.h" -#endif - -namespace webf::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 webf::binding::qjs diff --git a/bridge/bindings/qjs/bom/location.cc b/bridge/bindings/qjs/bom/location.cc deleted file mode 100644 index 3d779868d6..0000000000 --- a/bridge/bindings/qjs/bom/location.cc +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "location.h" -#include -#include "dart_methods.h" - -namespace webf::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 webf::binding::qjs diff --git a/bridge/bindings/qjs/bom/location.h b/bridge/bindings/qjs/bom/location.h deleted file mode 100644 index dbc0a26d71..0000000000 --- a/bridge/bindings/qjs/bom/location.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_LOCATION_H -#define BRIDGE_LOCATION_H - -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/host_object.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_LOCATION_H diff --git a/bridge/bindings/qjs/bom/performance.cc b/bridge/bindings/qjs/bom/performance.cc deleted file mode 100644 index 3007349021..0000000000 --- a/bridge/bindings/qjs/bom/performance.cc +++ /dev/null @@ -1,599 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "performance.h" -#include -#include "dart_methods.h" - -#define PERFORMANCE_ENTRY_NONE_UNIQUE_ID -1024 - -namespace webf::binding::qjs { - -void bindPerformance(ExecutionContext* context) { - auto* performance = Performance::instance(context); - context->defineGlobalProperty("performance", performance->jsObject); -} - -using namespace std::chrono; - -IMPL_PROPERTY_GETTER(PerformanceEntry, name)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* entry = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewString(ctx, entry->m_nativePerformanceEntry->name); -} - -IMPL_PROPERTY_GETTER(PerformanceEntry, entryType)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* entry = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewString(ctx, entry->m_nativePerformanceEntry->entryType); -} - -IMPL_PROPERTY_GETTER(PerformanceEntry, startTime)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* entry = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewUint32(ctx, entry->m_nativePerformanceEntry->startTime); -} - -IMPL_PROPERTY_GETTER(PerformanceEntry, duration)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* entry = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewUint32(ctx, entry->m_nativePerformanceEntry->duration); -} - -IMPL_PROPERTY_GETTER(Performance, timeOrigin)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - int64_t time = std::chrono::duration_cast(performance->m_context->timeOrigin.time_since_epoch()).count(); - return JS_NewUint32(ctx, time); -} - -JSValue Performance::now(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, performance->internalNow()); -} -JSValue Performance::toJSON(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - double now = performance->internalNow(); - int64_t timeOrigin = std::chrono::duration_cast(performance->m_context->timeOrigin.time_since_epoch()).count(); - - JSValue object = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, object, "now", JS_NewFloat64(ctx, now)); - JS_SetPropertyStr(ctx, object, "timeOrigin", JS_NewUint32(ctx, timeOrigin)); - return object; -} - -static JSValue buildPerformanceEntry(const std::string& entryType, ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) { - if (entryType == "mark") { - auto* mark = new PerformanceMark(context, nativePerformanceEntry); - return mark->jsObject; - } else if (entryType == "measure") { - auto* measure = new PerformanceMeasure(context, nativePerformanceEntry); - return measure->jsObject; - } - return JS_NULL; -} - -JSValue Performance::clearMarks(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - JSValue targetMark = JS_NULL; - if (argc == 1) { - targetMark = argv[0]; - } - - auto* entries = performance->m_nativePerformance.entries; - auto it = std::begin(*entries); - - while (it != entries->end()) { - char* entryType = (*it)->entryType; - if (strcmp(entryType, "mark") == 0) { - if (JS_IsNull(targetMark)) { - entries->erase(it); - } else { - std::string entryName = (*it)->name; - std::string targetName = jsValueToStdString(ctx, targetMark); - if (entryName == targetName) { - entries->erase(it); - } else { - it++; - }; - } - } else { - it++; - } - } - - return JS_NULL; -} -JSValue Performance::clearMeasures(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - JSValue targetMark = JS_NULL; - if (argc == 1) { - targetMark = argv[0]; - } - - auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - auto entries = performance->m_nativePerformance.entries; - auto it = std::begin(*entries); - - while (it != entries->end()) { - char* entryType = (*it)->entryType; - if (strcmp(entryType, "measure") == 0) { - if (JS_IsNull(targetMark)) { - entries->erase(it); - } else { - std::string entryName = (*it)->name; - std::string targetName = jsValueToStdString(ctx, targetMark); - if (entryName == targetName) { - entries->erase(it); - } else { - it++; - } - } - } else { - it++; - } - } - - return JS_NULL; -} -JSValue Performance::getEntries(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - auto entries = performance->getFullEntries(); - - size_t entriesSize = entries.size(); - JSValue returnArray = JS_NewArray(ctx); - JSValue pushMethod = JS_GetPropertyStr(ctx, returnArray, "push"); - - for (size_t i = 0; i < entriesSize; i++) { - auto& entry = entries[i]; - auto entryType = std::string(entry->entryType); - JSValue v = buildPerformanceEntry(entryType, performance->m_context, entry); - JS_Call(ctx, pushMethod, returnArray, 1, &v); - JS_FreeValue(ctx, v); - } - - JS_FreeValue(ctx, pushMethod); - return returnArray; -} -JSValue Performance::getEntriesByName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc == 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); - } - - std::string targetName = jsValueToStdString(ctx, argv[0]); - auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - auto entries = performance->getFullEntries(); - JSValue targetEntriesArray = JS_NewArray(ctx); - JSValue pushMethod = JS_GetPropertyStr(ctx, targetEntriesArray, "push"); - - for (auto& m_entries : entries) { - if (m_entries->name == targetName) { - std::string entryType = std::string(m_entries->entryType); - JSValue entry = buildPerformanceEntry(entryType, performance->m_context, m_entries); - JS_Call(ctx, pushMethod, targetEntriesArray, 1, &entry); - } - } - - JS_FreeValue(ctx, pushMethod); - return targetEntriesArray; -} -JSValue Performance::getEntriesByType(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc == 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); - } - - std::string entryType = jsValueToStdString(ctx, argv[0]); - auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - auto entries = performance->getFullEntries(); - JSValue targetEntriesArray = JS_NewArray(ctx); - JSValue pushMethod = JS_GetPropertyStr(ctx, targetEntriesArray, "push"); - - for (auto& m_entries : entries) { - if (m_entries->entryType == entryType) { - JSValue entry = buildPerformanceEntry(entryType, performance->m_context, m_entries); - JS_Call(ctx, pushMethod, targetEntriesArray, 1, &entry); - } - } - - JS_FreeValue(ctx, pushMethod); - return targetEntriesArray; -} -JSValue Performance::mark(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc != 1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'mark' on 'Performance': 1 argument required, but only 0 present."); - } - - auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - std::string markName = jsValueToStdString(ctx, argv[0]); - performance->m_nativePerformance.mark(markName); - - return JS_NULL; -} -JSValue Performance::measure(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc == 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 present."); - } - - std::string name = jsValueToStdString(ctx, argv[0]); - std::string startMark; - std::string endMark; - - if (argc > 1) { - if (!JS_IsUndefined(argv[1])) { - startMark = jsValueToStdString(ctx, argv[1]); - } - } - - if (argc > 2) { - endMark = jsValueToStdString(ctx, argv[2]); - } - - auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - JSValue exception = JS_NULL; - performance->internalMeasure(name, startMark, endMark, &exception); - - if (!JS_IsNull(exception)) - return exception; - - return JS_NULL; -} - -PerformanceEntry::PerformanceEntry(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) - : HostObject(context, "PerformanceEntry"), m_nativePerformanceEntry(nativePerformanceEntry) {} - -PerformanceMark::PerformanceMark(ExecutionContext* context, std::string& name, int64_t startTime) - : PerformanceEntry(context, new NativePerformanceEntry(name, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} -PerformanceMark::PerformanceMark(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) : PerformanceEntry(context, nativePerformanceEntry) {} -PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, std::string& name, int64_t startTime, int64_t duration) - : PerformanceEntry(context, new NativePerformanceEntry(name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} -PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) : PerformanceEntry(context, nativePerformanceEntry) {} -void NativePerformance::mark(const std::string& markName) { - int64_t startTime = std::chrono::duration_cast(system_clock::now().time_since_epoch()).count(); - auto* nativePerformanceEntry = new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; - entries->emplace_back(nativePerformanceEntry); -} -void NativePerformance::mark(const std::string& markName, int64_t startTime) { - auto* nativePerformanceEntry = new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; - entries->emplace_back(nativePerformanceEntry); -} - -Performance::Performance(ExecutionContext* context) : HostObject(context, "Performance") {} -void Performance::internalMeasure(const std::string& name, const std::string& startMark, const std::string& endMark, JSValue* exception) { - auto entries = getFullEntries(); - - if (!startMark.empty() && !endMark.empty()) { - size_t startMarkCount = std::count_if(entries.begin(), entries.end(), [&startMark](NativePerformanceEntry* entry) -> bool { return entry->name == startMark; }); - - if (startMarkCount == 0) { - *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not exist.", startMark.c_str()); - return; - } - - size_t endMarkCount = std::count_if(entries.begin(), entries.end(), [&endMark](NativePerformanceEntry* entry) -> bool { return entry->name == endMark; }); - - if (endMarkCount == 0) { - *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not exist.", endMark.c_str()); - return; - } - - if (startMarkCount != endMarkCount) { - *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s and %s does not appear the same number of times", startMark.c_str(), endMark.c_str()); - return; - } - - auto startIt = std::begin(entries); - auto endIt = std::begin(entries); - - for (size_t i = 0; i < startMarkCount; i++) { - auto startEntry = std::find_if(startIt, entries.end(), [&startMark](NativePerformanceEntry* entry) -> bool { return entry->name == startMark; }); - - bool isStartEntryHasUniqueId = (*startEntry)->uniqueId != PERFORMANCE_ENTRY_NONE_UNIQUE_ID; - - auto endEntryComparator = [&endMark, &startEntry, isStartEntryHasUniqueId](NativePerformanceEntry* entry) -> bool { - if (isStartEntryHasUniqueId) { - return entry->uniqueId == (*startEntry)->uniqueId && entry->name == endMark; - } - return entry->name == endMark; - }; - - auto endEntry = std::find_if(startEntry, entries.end(), endEntryComparator); - - if (endEntry == entries.end()) { - size_t startIndex = startEntry - entries.begin(); - assert_m(false, ("Can not get endEntry. startIndex: " + std::to_string(startIndex) + " startMark: " + startMark + " endMark: " + endMark)); - } - - int64_t duration = (*endEntry)->startTime - (*startEntry)->startTime; - int64_t startTime = std::chrono::duration_cast(system_clock::now().time_since_epoch()).count(); - auto* nativePerformanceEntry = new NativePerformanceEntry{name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; - m_nativePerformance.entries->emplace_back(nativePerformanceEntry); - startIt = ++startEntry; - endIt = ++endEntry; - } - } -} -double Performance::internalNow() { - auto now = std::chrono::system_clock::now(); - auto duration = std::chrono::duration_cast(now - m_context->timeOrigin); - auto reducedDuration = std::floor(duration / 1000us) * 1000us; - return std::chrono::duration_cast(reducedDuration).count(); -} -std::vector Performance::getFullEntries() { - auto* bridgeEntries = m_nativePerformance.entries; -#if ENABLE_PROFILE - if (getDartMethod()->getPerformanceEntries == nullptr) { - return std::vector(); - } - auto dartEntryList = getDartMethod()->getPerformanceEntries(m_context->getContextId()); - if (dartEntryList == nullptr) - return std::vector(); - auto dartEntityBytes = dartEntryList->entries; - std::vector dartEntries; - dartEntries.reserve(dartEntryList->length); - - for (size_t i = 0; i < dartEntryList->length * 3; i += 3) { - const char* name = reinterpret_cast(dartEntityBytes[i]); - int64_t startTime = dartEntityBytes[i + 1]; - int64_t uniqueId = dartEntityBytes[i + 2]; - auto* nativePerformanceEntry = new NativePerformanceEntry(name, "mark", startTime, 0, uniqueId); - dartEntries.emplace_back(nativePerformanceEntry); - } -#endif - - std::vector mergedEntries; - - mergedEntries.insert(mergedEntries.end(), bridgeEntries->begin(), bridgeEntries->end()); -#if ENABLE_PROFILE - mergedEntries.insert(mergedEntries.end(), dartEntries.begin(), dartEntries.end()); - delete[] dartEntryList->entries; - delete dartEntryList; -#endif - - return mergedEntries; -} - -#if ENABLE_PROFILE - -void Performance::measureSummary(JSValue* exception) { - internalMeasure(PERF_WIDGET_CREATION_COST, PERF_CONTROLLER_INIT_START, PERF_CONTROLLER_INIT_END, exception); - internalMeasure(PERF_CONTROLLER_PROPERTIES_INIT_COST, PERF_CONTROLLER_INIT_START, PERF_CONTROLLER_PROPERTY_INIT, exception); - internalMeasure(PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST, PERF_VIEW_CONTROLLER_INIT_START, PERF_VIEW_CONTROLLER_PROPERTY_INIT, exception); - internalMeasure(PERF_BRIDGE_INIT_COST, PERF_BRIDGE_INIT_START, PERF_BRIDGE_INIT_END, exception); - internalMeasure(PERF_BRIDGE_REGISTER_DART_METHOD_COST, PERF_BRIDGE_REGISTER_DART_METHOD_START, PERF_BRIDGE_REGISTER_DART_METHOD_END, exception); - internalMeasure(PERF_CREATE_VIEWPORT_COST, PERF_CREATE_VIEWPORT_START, PERF_CREATE_VIEWPORT_END, exception); - internalMeasure(PERF_ELEMENT_MANAGER_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, PERF_ELEMENT_MANAGER_INIT_END, exception); - internalMeasure(PERF_ELEMENT_MANAGER_PROPERTIES_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, PERF_ELEMENT_MANAGER_PROPERTY_INIT, exception); - internalMeasure(PERF_ROOT_ELEMENT_INIT_COST, PERF_ROOT_ELEMENT_INIT_START, PERF_ROOT_ELEMENT_INIT_END, exception); - internalMeasure(PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST, PERF_ROOT_ELEMENT_INIT_START, PERF_ROOT_ELEMENT_PROPERTY_INIT, exception); - internalMeasure(PERF_JS_CONTEXT_INIT_COST, PERF_JS_CONTEXT_INIT_START, PERF_JS_CONTEXT_INIT_END, exception); - internalMeasure(PERF_JS_HOST_CLASS_GET_PROPERTY_COST, PERF_JS_HOST_CLASS_GET_PROPERTY_START, PERF_JS_HOST_CLASS_GET_PROPERTY_END, exception); - internalMeasure(PERF_JS_HOST_CLASS_SET_PROPERTY_COST, PERF_JS_HOST_CLASS_SET_PROPERTY_START, PERF_JS_HOST_CLASS_SET_PROPERTY_END, exception); - internalMeasure(PERF_JS_HOST_CLASS_INIT_COST, PERF_JS_HOST_CLASS_INIT_START, PERF_JS_HOST_CLASS_INIT_END, exception); - internalMeasure(PERF_JS_NATIVE_FUNCTION_CALL_COST, PERF_JS_NATIVE_FUNCTION_CALL_START, PERF_JS_NATIVE_FUNCTION_CALL_END, exception); - internalMeasure(PERF_JS_NATIVE_METHOD_INIT_COST, PERF_JS_NATIVE_METHOD_INIT_START, PERF_JS_NATIVE_METHOD_INIT_END, exception); - internalMeasure(PERF_JS_POLYFILL_INIT_COST, PERF_JS_POLYFILL_INIT_START, PERF_JS_POLYFILL_INIT_END, exception); - internalMeasure(PERF_JS_BUNDLE_LOAD_COST, PERF_JS_BUNDLE_LOAD_START, PERF_JS_BUNDLE_LOAD_END, exception); - internalMeasure(PERF_JS_BUNDLE_EVAL_COST, PERF_JS_BUNDLE_EVAL_START, PERF_JS_BUNDLE_EVAL_END, exception); - internalMeasure(PERF_FLUSH_UI_COMMAND_COST, PERF_FLUSH_UI_COMMAND_START, PERF_FLUSH_UI_COMMAND_END, exception); - internalMeasure(PERF_CREATE_ELEMENT_COST, PERF_CREATE_ELEMENT_START, PERF_CREATE_ELEMENT_END, exception); - internalMeasure(PERF_CREATE_TEXT_NODE_COST, PERF_CREATE_TEXT_NODE_START, PERF_CREATE_TEXT_NODE_END, exception); - internalMeasure(PERF_CREATE_COMMENT_COST, PERF_CREATE_COMMENT_START, PERF_CREATE_COMMENT_END, exception); - internalMeasure(PERF_DISPOSE_EVENT_TARGET_COST, PERF_DISPOSE_EVENT_TARGET_START, PERF_DISPOSE_EVENT_TARGET_END, exception); - internalMeasure(PERF_ADD_EVENT_COST, PERF_ADD_EVENT_START, PERF_ADD_EVENT_END, exception); - internalMeasure(PERF_INSERT_ADJACENT_NODE_COST, PERF_INSERT_ADJACENT_NODE_START, PERF_INSERT_ADJACENT_NODE_END, exception); - internalMeasure(PERF_REMOVE_NODE_COST, PERF_REMOVE_NODE_START, PERF_REMOVE_NODE_END, exception); - internalMeasure(PERF_SET_STYLE_COST, PERF_SET_STYLE_START, PERF_SET_STYLE_END, exception); - internalMeasure(PERF_PARSE_CSS_COST, PERF_PARSE_CSS_START, PERF_PARSE_CSS_END, exception); - internalMeasure(PERF_PARSE_INLINE_CSS_COST, PERF_PARSE_INLINE_CSS_START, PERF_PARSE_INLINE_CSS_END, exception); - internalMeasure(PERF_MATCH_ELEMENT_RULE_COST, PERF_MATCH_ELEMENT_RULE_START, PERF_MATCH_ELEMENT_RULE_END, exception); - internalMeasure(PERF_FLUSH_STYLE_COST, PERF_FLUSH_STYLE_START, PERF_FLUSH_STYLE_END, exception); - internalMeasure(PERF_SET_PROPERTIES_COST, PERF_SET_PROPERTIES_START, PERF_SET_PROPERTIES_END, exception); - internalMeasure(PERF_REMOVE_PROPERTIES_COST, PERF_REMOVE_PROPERTIES_START, PERF_REMOVE_PROPERTIES_END, exception); - internalMeasure(PERF_FLEX_LAYOUT_COST, PERF_FLEX_LAYOUT_START, PERF_FLEX_LAYOUT_END, exception); - internalMeasure(PERF_FLOW_LAYOUT_COST, PERF_FLOW_LAYOUT_START, PERF_FLOW_LAYOUT_END, exception); - internalMeasure(PERF_INTRINSIC_LAYOUT_COST, PERF_INTRINSIC_LAYOUT_START, PERF_INTRINSIC_LAYOUT_END, exception); - internalMeasure(PERF_SILVER_LAYOUT_COST, PERF_SILVER_LAYOUT_START, PERF_SILVER_LAYOUT_END, exception); - internalMeasure(PERF_PAINT_COST, PERF_PAINT_START, PERF_PAINT_END, exception); - internalMeasure(PERF_DOM_FORCE_LAYOUT_COST, PERF_DOM_FORCE_LAYOUT_START, PERF_DOM_FORCE_LAYOUT_END, exception); - internalMeasure(PERF_DOM_FLUSH_UI_COMMAND_COST, PERF_DOM_FLUSH_UI_COMMAND_START, PERF_DOM_FLUSH_UI_COMMAND_END, exception); - internalMeasure(PERF_JS_PARSE_TIME_COST, PERF_JS_PARSE_TIME_START, PERF_JS_PARSE_TIME_END, exception); -} - -std::vector findAllMeasures(const std::vector& entries, const std::string& targetName) { - std::vector resultEntries; - - for (auto entry : entries) { - if (entry->name == targetName) { - resultEntries.emplace_back(entry); - } - } - - return resultEntries; -}; - -double getMeasureTotalDuration(const std::vector& measures) { - double duration = 0.0; - for (auto entry : measures) { - duration += entry->duration; - } - return duration / 1000; -} - -JSValue Performance::__webf_navigation_summary__(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - JSValue exception = JS_NULL; - performance->measureSummary(&exception); - - std::vector entries = performance->getFullEntries(); - - if (entries.empty()) { - return JS_ThrowTypeError(ctx, "Failed to get navigation summary: flutter is not running in profile mode."); - } - - std::vector measures; - for (auto& m_entries : entries) { - if (std::string(m_entries->entryType) == "measure") { - measures.emplace_back(m_entries); - } - } - -#define GET_COST_WITH_DECREASE(NAME, MACRO, DECREASE) \ - auto NAME##Measures = findAllMeasures(measures, MACRO); \ - size_t NAME##Count = NAME##Measures.size(); \ - double NAME##Cost = getMeasureTotalDuration(NAME##Measures) - (DECREASE); \ - auto NAME##Avg = NAME##Measures.empty() ? 0 : (NAME##Cost) / NAME##Measures.size(); - -#define GET_COST(NAME, MACRO) \ - auto NAME##Measures = findAllMeasures(measures, MACRO); \ - size_t NAME##Count = NAME##Measures.size(); \ - double NAME##Cost = getMeasureTotalDuration(NAME##Measures); \ - auto NAME##Avg = NAME##Measures.empty() ? 0 : NAME##Cost / NAME##Measures.size(); - - GET_COST(widgetCreation, PERF_WIDGET_CREATION_COST); - GET_COST(controllerPropertiesInit, PERF_CONTROLLER_PROPERTIES_INIT_COST); - GET_COST(viewControllerPropertiesInit, PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST); - GET_COST(bridgeInit, PERF_BRIDGE_INIT_COST); - GET_COST(bridgeRegisterDartMethod, PERF_BRIDGE_REGISTER_DART_METHOD_COST); - GET_COST(createViewport, PERF_CREATE_VIEWPORT_COST); - GET_COST(elementManagerInit, PERF_ELEMENT_MANAGER_INIT_COST); - GET_COST(elementManagerPropertiesInit, PERF_ELEMENT_MANAGER_PROPERTIES_INIT_COST); - GET_COST(rootElementInit, PERF_ROOT_ELEMENT_INIT_COST); - GET_COST(rootElementPropertiesInit, PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST); - GET_COST(jsContextInit, PERF_JS_CONTEXT_INIT_COST); - GET_COST(jsNativeMethodInit, PERF_JS_NATIVE_METHOD_INIT_COST); - GET_COST(jsPolyfillInit, PERF_JS_POLYFILL_INIT_COST); - GET_COST(jsBundleLoad, PERF_JS_BUNDLE_LOAD_COST); - GET_COST(jsParseTime, PERF_JS_PARSE_TIME_COST); - GET_COST(flushUiCommand, PERF_FLUSH_UI_COMMAND_COST); - GET_COST(createElement, PERF_CREATE_ELEMENT_COST); - GET_COST(createTextNode, PERF_CREATE_TEXT_NODE_COST); - GET_COST(createComment, PERF_CREATE_COMMENT_COST); - GET_COST(disposeEventTarget, PERF_DISPOSE_EVENT_TARGET_COST); - GET_COST(addEvent, PERF_ADD_EVENT_COST); - GET_COST(insertAdjacentNode, PERF_INSERT_ADJACENT_NODE_COST); - GET_COST(removeNode, PERF_REMOVE_NODE_COST); - GET_COST(setStyle, PERF_SET_STYLE_COST); - GET_COST(setProperties, PERF_SET_PROPERTIES_COST); - GET_COST(parseCss, PERF_PARSE_CSS_COST); - GET_COST(parseInlineCss, PERF_PARSE_INLINE_CSS_COST); - GET_COST(matchElementRule, PERF_MATCH_ELEMENT_RULE_COST); - GET_COST(flushStyle, PERF_FLUSH_STYLE_COST); - GET_COST(removeProperties, PERF_REMOVE_PROPERTIES_COST); - GET_COST(flexLayout, PERF_FLEX_LAYOUT_COST); - GET_COST(flowLayout, PERF_FLOW_LAYOUT_COST); - GET_COST(intrinsicLayout, PERF_INTRINSIC_LAYOUT_COST); - GET_COST(silverLayout, PERF_SILVER_LAYOUT_COST); - GET_COST(paint, PERF_PAINT_COST); - GET_COST(domForceLayout, PERF_DOM_FORCE_LAYOUT_COST); - GET_COST(domFlushUICommand, PERF_DOM_FLUSH_UI_COMMAND_COST); - GET_COST_WITH_DECREASE(jsHostClassGetProperty, PERF_JS_HOST_CLASS_GET_PROPERTY_COST, domForceLayoutCost + domFlushUICommandCost) - GET_COST(jsHostClassSetProperty, PERF_JS_HOST_CLASS_SET_PROPERTY_COST); - GET_COST(jsHostClassInit, PERF_JS_HOST_CLASS_INIT_COST); - GET_COST(jsNativeFunction, PERF_JS_NATIVE_FUNCTION_CALL_COST); - GET_COST_WITH_DECREASE(jsBundleEval, PERF_JS_BUNDLE_EVAL_COST, domForceLayoutCost + domFlushUICommandCost); - - double initBundleCost = jsBundleLoadCost + jsBundleEvalCost + flushUiCommandCost + createElementCost + createTextNodeCost + createCommentCost + disposeEventTargetCost + addEventCost + - insertAdjacentNodeCost + removeNodeCost + setStyleCost + setPropertiesCost + removePropertiesCost; - // layout and paint measure are not correct. - double renderingCost = flexLayoutCost + flowLayoutCost + intrinsicLayoutCost + silverLayoutCost + paintCost; - double totalCost = widgetCreationCost + initBundleCost; - - char buffer[5000]; - // clang-format off - sprintf(buffer, R"( -Total time cost(without paint and layout): %.*fms - -%s: %.*fms - + %s %.*fms - + %s %.*fms - + %s %.*fms - + %s %.*fms - + %s %.*fms - + %s %.*fms - + %s %.*fms - + %s %.*fms - + %s %.*fms - + %s %.*fms - + %s %.*fms - + %s %.*fms -First Bundle Load: %.*fms - + %s %.*fms - + %s %.*fms - + %s %.*fms - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu -Rendering: %.*fms - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu - + %s %.*fms avg: %.*fms count: %zu -)", - 2, totalCost, - PERF_WIDGET_CREATION_COST, 2, widgetCreationCost, - PERF_CONTROLLER_PROPERTIES_INIT_COST, 2, controllerPropertiesInitCost, - PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST, 2, viewControllerPropertiesInitCost, - PERF_ELEMENT_MANAGER_INIT_COST, 2, elementManagerInitCost, - PERF_ELEMENT_MANAGER_PROPERTY_INIT, 2, elementManagerPropertiesInitCost, - PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST, 2, rootElementPropertiesInitCost, - PERF_ROOT_ELEMENT_INIT_COST, 2, rootElementInitCost, - PERF_CREATE_VIEWPORT_COST, 2, createViewportCost, - PERF_BRIDGE_INIT_COST, 2, bridgeInitCost, - PERF_BRIDGE_REGISTER_DART_METHOD_COST, 2, bridgeRegisterDartMethodCost, - PERF_JS_CONTEXT_INIT_COST, 2, jsContextInitCost, - PERF_JS_NATIVE_METHOD_INIT_COST, 2, jsNativeMethodInitCost, - PERF_JS_POLYFILL_INIT_COST, 2, jsPolyfillInitCost, - 2, initBundleCost, - PERF_JS_BUNDLE_LOAD_COST, 2, jsBundleLoadCost, - PERF_JS_BUNDLE_EVAL_COST, 2, jsBundleEvalCost, - PERF_JS_PARSE_TIME_COST, 2, jsParseTimeCost, - PERF_FLUSH_UI_COMMAND_COST, 2, flushUiCommandCost, 2, flushUiCommandAvg, flushUiCommandCount, - PERF_CREATE_ELEMENT_COST, 2, createElementCost, 2, createElementAvg, createElementCount, - PERF_JS_HOST_CLASS_GET_PROPERTY_COST, 2, jsHostClassGetPropertyCost, 2, jsHostClassGetPropertyAvg, jsHostClassGetPropertyCount, - PERF_JS_HOST_CLASS_SET_PROPERTY_COST, 2, jsHostClassSetPropertyCost, 2, jsHostClassSetPropertyAvg, jsHostClassSetPropertyCount, - PERF_JS_HOST_CLASS_INIT_COST, 2, jsHostClassInitCost, 2, jsHostClassInitAvg, jsHostClassInitCount, - PERF_JS_NATIVE_FUNCTION_CALL_COST, 2, jsNativeFunctionCost, 2, jsNativeFunctionAvg, jsNativeFunctionCount, - PERF_CREATE_TEXT_NODE_COST, 2, createTextNodeCost, 2, createTextNodeAvg, createTextNodeCount, - PERF_CREATE_COMMENT_COST, 2, createCommentCost, 2, createCommentAvg, createCommentCount, - PERF_DISPOSE_EVENT_TARGET_COST, 2, disposeEventTargetCost, 2, disposeEventTargetAvg, disposeEventTargetCount, - PERF_ADD_EVENT_COST, 2, addEventCost, 2, addEventAvg, addEventCount, - PERF_INSERT_ADJACENT_NODE_COST, 2, insertAdjacentNodeCost, 2, insertAdjacentNodeAvg, insertAdjacentNodeCount, - PERF_REMOVE_NODE_COST, 2, removeNodeCost, 2, removeNodeAvg, removeNodeCount, - PERF_SET_STYLE_COST, 2, setStyleCost, 2, setStyleAvg, setStyleCount, - PERF_PARSE_CSS_COST, 2, parseCssCost, 2, parseCssAvg, parseCssCount, - PERF_PARSE_INLINE_CSS_COST, 2, parseInlineCssCost, 2, parseInlineCssAvg, parseInlineCssCount, - PERF_MATCH_ELEMENT_RULE_COST, 2, matchElementRuleCost, 2, matchElementRuleAvg, matchElementRuleCount, - PERF_FLUSH_STYLE_COST, 2, flushStyleCost, 2, flushStyleAvg, flushStyleCount, - PERF_DOM_FORCE_LAYOUT_COST, 2, domForceLayoutCost, 2, domForceLayoutAvg, domForceLayoutCount, - PERF_DOM_FLUSH_UI_COMMAND_COST, 2, domFlushUICommandCost, 2, domFlushUICommandAvg, domFlushUICommandCount, - PERF_SET_PROPERTIES_COST, 2, setPropertiesCost, 2, setPropertiesAvg, setPropertiesCount, - PERF_REMOVE_PROPERTIES_COST, 2, removePropertiesCost, 2, removePropertiesAvg, removePropertiesCount, - 2, renderingCost, - PERF_FLEX_LAYOUT_COST, 2, flexLayoutCost, 2, flexLayoutAvg, flexLayoutCount, - PERF_FLOW_LAYOUT_COST, 2, flowLayoutCost, 2, flowLayoutAvg, flowLayoutCount, - PERF_INTRINSIC_LAYOUT_COST, 2, intrinsicLayoutCost, 2, intrinsicLayoutAvg, intrinsicLayoutCount, - PERF_SILVER_LAYOUT_COST, 2, silverLayoutCost, 2, silverLayoutAvg, silverLayoutCount, - PERF_PAINT_COST, 2, paintCost, 2, paintAvg, paintCount - ); - // clang-format on - return JS_NewString(ctx, buffer); -} - -#endif - -} // namespace webf::binding::qjs diff --git a/bridge/bindings/qjs/bom/performance.h b/bridge/bindings/qjs/bom/performance.h deleted file mode 100644 index fd02767ac2..0000000000 --- a/bridge/bindings/qjs/bom/performance.h +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_PERFORMANCE_H -#define BRIDGE_PERFORMANCE_H - -#if ENABLE_PROFILE -#define PERF_WIDGET_CREATION_COST "widget_creation_cost" -#define PERF_CONTROLLER_PROPERTIES_INIT_COST "controller_properties_init_cost" -#define PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST "view_controller_properties_init_cost" -#define PERF_BRIDGE_INIT_COST "bridge_init_cost" -#define PERF_BRIDGE_REGISTER_DART_METHOD_COST "bridge_register_dart_method_cost" -#define PERF_CREATE_VIEWPORT_COST "create_viewport" -#define PERF_ELEMENT_MANAGER_INIT_COST "element_manager_init_cost" -#define PERF_ELEMENT_MANAGER_PROPERTIES_INIT_COST "element_manager_property_init_cost" -#define PERF_ROOT_ELEMENT_INIT_COST "root_element_init_cost" -#define PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST "root_element_property_init_cost" -#define PERF_JS_CONTEXT_INIT_COST "js_context_init_cost" -#define PERF_JS_NATIVE_METHOD_INIT_COST "native_method_init_cost" -#define PERF_JS_POLYFILL_INIT_COST "polyfill_init_cost" -#define PERF_JS_BUNDLE_LOAD_COST "js_bundle_load_cost" -#define PERF_JS_BUNDLE_EVAL_COST "js_bundle_eval_cost" -#define PERF_JS_PARSE_TIME_COST "js_parse_time_cost" -#define PERF_JS_HOST_CLASS_INIT_COST "js_host_class_init_cost" -#define PERF_JS_NATIVE_FUNCTION_CALL_COST "js_native_function_call_cost" -#define PERF_JS_HOST_CLASS_GET_PROPERTY_COST "js_host_class_get_property_cost" -#define PERF_JS_HOST_CLASS_SET_PROPERTY_COST "js_host_class_set_property_cost" -#define PERF_FLUSH_UI_COMMAND_COST "flush_ui_command_cost" -#define PERF_CREATE_ELEMENT_COST "create_element_cost" -#define PERF_CREATE_TEXT_NODE_COST "create_text_node_cost" -#define PERF_CREATE_COMMENT_COST "create_comment_cost" -#define PERF_DISPOSE_EVENT_TARGET_COST "dispose_event_target_cost" -#define PERF_ADD_EVENT_COST "add_event_cost" -#define PERF_INSERT_ADJACENT_NODE_COST "insert_adjacent_node_cost" -#define PERF_REMOVE_NODE_COST "remove_node_cost" -#define PERF_SET_STYLE_COST "set_style_cost" -#define PERF_PARSE_CSS_COST "parse_css_cost" -#define PERF_PARSE_INLINE_CSS_COST "parse_inline_css_cost" -#define PERF_MATCH_ELEMENT_RULE_COST "match_element_rule_cost" -#define PERF_FLUSH_STYLE_COST "flush_style_cost" -#define PERF_DOM_FORCE_LAYOUT_COST "dom_force_layout_cost" -#define PERF_DOM_FLUSH_UI_COMMAND_COST "dom_flush_ui_command_cost" -#define PERF_SET_PROPERTIES_COST "set_properties_cost" -#define PERF_REMOVE_PROPERTIES_COST "remove_properties_cost" -#define PERF_FLEX_LAYOUT_COST "flex_layout_cost" -#define PERF_FLOW_LAYOUT_COST "flow_layout_cost" -#define PERF_INTRINSIC_LAYOUT_COST "intrinsic_layout_cost" -#define PERF_SILVER_LAYOUT_COST "silver_layout_cost" -#define PERF_PAINT_COST "paint_cost" - -#define PERF_CONTROLLER_INIT_START "controller_init_start" -#define PERF_CONTROLLER_INIT_END "controller_init_end" -#define PERF_CONTROLLER_PROPERTY_INIT "controller_properties_init" -#define PERF_VIEW_CONTROLLER_INIT_START "view_controller_init_start" -#define PERF_VIEW_CONTROLLER_PROPERTY_INIT "view_controller_property_init" -#define PERF_BRIDGE_INIT_START "bridge_init_start" -#define PERF_BRIDGE_INIT_END "bridge_init_end" -#define PERF_BRIDGE_REGISTER_DART_METHOD_START "bridge_register_dart_method_start" -#define PERF_BRIDGE_REGISTER_DART_METHOD_END "bridge_register_dart_method_end" -#define PERF_CREATE_VIEWPORT_START "create_viewport_start" -#define PERF_CREATE_VIEWPORT_END "create_viewport_end" -#define PERF_ELEMENT_MANAGER_INIT_START "element_manager_init_start" -#define PERF_ELEMENT_MANAGER_INIT_END "element_manager_init_end" -#define PERF_ELEMENT_MANAGER_PROPERTY_INIT "element_manager_property_init" -#define PERF_ROOT_ELEMENT_INIT_START "root_element_init_start" -#define PERF_ROOT_ELEMENT_INIT_END "root_element_init_end" -#define PERF_ROOT_ELEMENT_PROPERTY_INIT "root_element_property_init" -#define PERF_JS_CONTEXT_INIT_START "js_context_start" -#define PERF_JS_CONTEXT_INIT_END "js_context_end" -#define PERF_JS_HOST_CLASS_GET_PROPERTY_START "js_host_class_get_property_start" -#define PERF_JS_HOST_CLASS_GET_PROPERTY_END "js_host_class_get_property_end" -#define PERF_JS_HOST_CLASS_SET_PROPERTY_START "js_host_class_set_property_start" -#define PERF_JS_HOST_CLASS_SET_PROPERTY_END "js_host_class_set_property_end" -#define PERF_JS_HOST_CLASS_INIT_START "js_host_class_init_start" -#define PERF_JS_HOST_CLASS_INIT_END "js_host_class_init_end" -#define PERF_JS_NATIVE_FUNCTION_CALL_START "js_native_function_call_start" -#define PERF_JS_NATIVE_FUNCTION_CALL_END "js_native_function_call_end" -#define PERF_JS_NATIVE_METHOD_INIT_START "init_native_method_start" -#define PERF_JS_NATIVE_METHOD_INIT_END "init_native_method_end" -#define PERF_JS_POLYFILL_INIT_START "init_js_polyfill_start" -#define PERF_JS_POLYFILL_INIT_END "init_js_polyfill_end" -#define PERF_JS_BUNDLE_LOAD_START "js_bundle_load_start" -#define PERF_JS_BUNDLE_LOAD_END "js_bundle_load_end" -#define PERF_JS_BUNDLE_EVAL_START "js_bundle_eval_start" -#define PERF_JS_BUNDLE_EVAL_END "js_bundle_eval_end" -#define PERF_JS_PARSE_TIME_START "js_parse_time_start" -#define PERF_JS_PARSE_TIME_END "js_parse_time_end" -#define PERF_FLUSH_UI_COMMAND_START "flush_ui_command_start" -#define PERF_FLUSH_UI_COMMAND_END "flush_ui_command_end" -#define PERF_CREATE_ELEMENT_START "create_element_start" -#define PERF_CREATE_ELEMENT_END "create_element_end" -#define PERF_CREATE_TEXT_NODE_START "create_text_node_start" -#define PERF_CREATE_TEXT_NODE_END "create_text_node_end" -#define PERF_CREATE_COMMENT_START "create_comment_start" -#define PERF_CREATE_COMMENT_END "create_comment_end" -#define PERF_DISPOSE_EVENT_TARGET_START "dispose_event_target_start" -#define PERF_DISPOSE_EVENT_TARGET_END "dispose_event_target_end" -#define PERF_ADD_EVENT_START "add_event_start" -#define PERF_ADD_EVENT_END "add_event_end" -#define PERF_INSERT_ADJACENT_NODE_START "insert_adjacent_node_start" -#define PERF_INSERT_ADJACENT_NODE_END "insert_adjacent_node_end" -#define PERF_REMOVE_NODE_START "remove_node_start" -#define PERF_REMOVE_NODE_END "remove_node_end" -#define PERF_SET_STYLE_START "set_style_start" -#define PERF_SET_STYLE_END "set_style_end" -#define PERF_PARSE_CSS_START "parse_css_start" -#define PERF_PARSE_CSS_END "parse_css_end" -#define PERF_PARSE_INLINE_CSS_START "parse_inline_css_start" -#define PERF_PARSE_INLINE_CSS_END "parse_inline_css_end" -#define PERF_MATCH_ELEMENT_RULE_START "match_element_rule_start" -#define PERF_MATCH_ELEMENT_RULE_END "match_element_rule_end" -#define PERF_FLUSH_STYLE_START "flush_style_start" -#define PERF_FLUSH_STYLE_END "flush_style_end" -#define PERF_DOM_FORCE_LAYOUT_START "dom_force_layout_start" -#define PERF_DOM_FORCE_LAYOUT_END "dom_force_layout_end" -#define PERF_DOM_FLUSH_UI_COMMAND_START "dom_flush_ui_command_start" -#define PERF_DOM_FLUSH_UI_COMMAND_END "dom_flush_ui_command_end" -#define PERF_SET_PROPERTIES_START "set_properties_start" -#define PERF_SET_PROPERTIES_END "set_properties_end" -#define PERF_REMOVE_PROPERTIES_START "remove_properties_start" -#define PERF_REMOVE_PROPERTIES_END "remove_properties_end" -#define PERF_FLEX_LAYOUT_START "flex_layout_start" -#define PERF_FLEX_LAYOUT_END "flex_layout_end" -#define PERF_FLOW_LAYOUT_START "flow_layout_start" -#define PERF_FLOW_LAYOUT_END "flow_layout_end" -#define PERF_INTRINSIC_LAYOUT_START "intrinsic_layout_start" -#define PERF_INTRINSIC_LAYOUT_END "intrinsic_layout_end" -#define PERF_SILVER_LAYOUT_START "silver_layout_start" -#define PERF_SILVER_LAYOUT_END "silver_layout_end" -#define PERF_PAINT_START "paint_start" -#define PERF_PAINT_END "paint_end" -#endif - -#include "bindings/qjs/host_object.h" - -namespace webf::binding::qjs { - -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) { - this->name = new char[name.size() + 1]; - this->entryType = new char[entryType.size() + 1]; - strcpy(this->name, name.data()); - strcpy(this->entryType, entryType.data()); - }; - char* name; - char* entryType; - int64_t startTime; - int64_t duration; - int64_t uniqueId; -}; - -class PerformanceEntry : public HostObject { - public: - PerformanceEntry() = delete; - explicit PerformanceEntry(ExecutionContext* context, NativePerformanceEntry* m_nativePerformanceEntry); - - DEFINE_READONLY_PROPERTY(name); - DEFINE_READONLY_PROPERTY(entryType); - DEFINE_READONLY_PROPERTY(startTime); - DEFINE_READONLY_PROPERTY(duration); - - private: - NativePerformanceEntry* m_nativePerformanceEntry{nullptr}; -}; - -class PerformanceMark : public PerformanceEntry { - public: - PerformanceMark() = delete; - explicit PerformanceMark(ExecutionContext* context, std::string& name, int64_t startTime); - explicit PerformanceMark(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry); -}; - -class PerformanceMeasure : public PerformanceEntry { - public: - PerformanceMeasure() = delete; - explicit PerformanceMeasure(ExecutionContext* context, std::string& name, int64_t startTime, int64_t duration); - explicit PerformanceMeasure(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry); -}; - -class NativePerformance { - public: - void mark(const std::string& markName); - void mark(const std::string& markName, int64_t startTime); - std::vector* entries{new std::vector()}; -}; - -class Performance : public HostObject { - public: - Performance() = delete; - explicit Performance(ExecutionContext* context); - - OBJECT_INSTANCE(Performance); - - static JSValue now(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue toJSON(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue clearMarks(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue clearMeasures(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getEntries(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getEntriesByName(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getEntriesByType(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue mark(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue measure(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - -#if ENABLE_PROFILE - static JSValue __webf_navigation_summary__(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - void measureSummary(JSValue* exception); -#endif - - NativePerformance m_nativePerformance; - - DEFINE_READONLY_PROPERTY(timeOrigin); - - private: - void internalMeasure(const std::string& name, const std::string& startMark, const std::string& endMark, JSValue* exception); - double internalNow(); - std::vector getFullEntries(); - - DEFINE_FUNCTION(now, 0); - DEFINE_FUNCTION(toJSON, 0); - DEFINE_FUNCTION(clearMarks, 1); - DEFINE_FUNCTION(clearMeasures, 1); - DEFINE_FUNCTION(getEntries, 0); - DEFINE_FUNCTION(getEntriesByName, 2); - DEFINE_FUNCTION(getEntriesByType, 1); - DEFINE_FUNCTION(mark, 1); - DEFINE_FUNCTION(measure, 4); - -#if ENABLE_PROFILE - DEFINE_FUNCTION(__webf_navigation_summary__, 0); -#endif -}; - -} // namespace webf::binding::qjs - -#endif // BRIDGE_PERFORMANCE_H diff --git a/bridge/bindings/qjs/bom/screen.cc b/bridge/bindings/qjs/bom/screen.cc deleted file mode 100644 index 5a31b108a0..0000000000 --- a/bridge/bindings/qjs/bom/screen.cc +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "screen.h" - -namespace webf::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 webf::binding::qjs diff --git a/bridge/bindings/qjs/bom/screen.h b/bridge/bindings/qjs/bom/screen.h deleted file mode 100644 index b302f545bf..0000000000 --- a/bridge/bindings/qjs/bom/screen.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_SCREEN_H -#define BRIDGE_SCREEN_H - -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/host_object.h" -#include "dart_methods.h" - -namespace webf::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 webf::binding::qjs - -class screen {}; - -#endif // BRIDGE_SCREEN_H diff --git a/bridge/bindings/qjs/bom/timer.cc b/bridge/bindings/qjs/bom/timer.cc deleted file mode 100644 index db99071355..0000000000 --- a/bridge/bindings/qjs/bom/timer.cc +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF 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 "webf_test_env.h" -#endif - -namespace webf::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 webf::binding::qjs diff --git a/bridge/bindings/qjs/bom/timer.h b/bridge/bindings/qjs/bom/timer.h deleted file mode 100644 index 07691f8662..0000000000 --- a/bridge/bindings/qjs/bom/timer.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_TIMER_H -#define BRIDGE_TIMER_H - -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/garbage_collected.h" -#include "dom_timer_coordinator.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_TIMER_H diff --git a/bridge/bindings/qjs/bom/window.cc b/bridge/bindings/qjs/bom/window.cc deleted file mode 100644 index fef2ea61c3..0000000000 --- a/bridge/bindings/qjs/bom/window.cc +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF 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 webf::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 '__webf_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"); - JSValue onerrorHandler = argv[0]; - window->setAttributesEventHandler(eventString, 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 webf::binding::qjs diff --git a/bridge/bindings/qjs/bom/window.h b/bridge/bindings/qjs/bom/window.h deleted file mode 100644 index 67656a08bc..0000000000 --- a/bridge/bindings/qjs/bom/window.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_WINDOW_H -#define BRIDGE_WINDOW_H - -#include "bindings/qjs/bom/location.h" -#include "bindings/qjs/dom/event_target.h" -#include "bindings/qjs/executing_context.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_WINDOW_H diff --git a/bridge/bindings/qjs/converter.h b/bridge/bindings/qjs/converter.h new file mode 100644 index 0000000000..e60d2b1cdb --- /dev/null +++ b/bridge/bindings/qjs/converter.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CONVERTER_H +#define BRIDGE_CONVERTER_H + +#include + +namespace webf { + +// 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 webf + +#endif // BRIDGE_CONVERTER_H diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h new file mode 100644 index 0000000000..6ae044357d --- /dev/null +++ b/bridge/bindings/qjs/converter_impl.h @@ -0,0 +1,480 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ +#define BRIDGE_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_handler.h" +#include "js_event_listener.h" +#include "native_string_utils.h" +#include "script_promise.h" + +namespace webf { + +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); + } +}; + +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) { + return ScriptValue(ctx, value); + } + + static JSValue ToValue(JSContext* ctx, const ScriptValue& value) { return JS_DupValue(ctx, value.QJSValue()); } +}; + +template <> +struct Converter> : public ConverterBase> { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsUndefined(value)) { + return ScriptValue::Empty(ctx); + } + + 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(); + 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(); + 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)); + + if (!JS_IsArray(ctx, value)) { + exception_state.ThrowException(ctx, ErrorType::TypeError, "The expected type of value is not array."); + return {}; + } + + 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++) { + JSValue iv = JS_GetPropertyUint32(ctx, value, i); + auto&& item = Converter::FromValue(ctx, iv, exception_state); + JS_FreeValue(ctx, iv); + + if (exception_state.HasException()) { + return {}; + } + + v.emplace_back(item); + } + + return v; + } + static JSValue ToValue(JSContext* ctx, ImplType value) { + JSValue array = JS_NewArray(ctx); + JS_SetPropertyStr(ctx, array, "length", Converter::ToValue(ctx, value.size())); + for (int i = 0; i < value.size(); i++) { + JS_SetPropertyUint32(ctx, array, i, Converter::ToValue(ctx, value[i])); + } + return array; + } +}; + +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 JSValue ToValue(JSContext* ctx, ImplType value) { return value.ToQuickJS(); } +}; + +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return JSEventHandler::CreateOrNull(ctx, value, JSEventHandler::HandlerType::kEventHandler); + } + + static JSValue ToValue(JSContext* ctx, ImplType value) { + if (DynamicTo(*value)) { + return To(*value).GetListenerObject(); + } + return JS_NULL; + } +}; + +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); + } + + static JSValue ToValue(JSContext* ctx, ImplType value) { + if (value == nullptr) { + return JS_NULL; + } + + return Converter::ToValue(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::value>>> + : ConverterBase { + static T* FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsNull(value)) { + return nullptr; + } + return Converter::FromValue(ctx, value, exception_state); + } + + static T* 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, T* value) { + if (value == nullptr) + return JS_NULL; + return Converter::ToValue(ctx, value); + } + + static JSValue ToValue(JSContext* ctx, const T* value) { + if (value == nullptr) + return JS_NULL; + return Converter::ToValue(ctx, value); + } +}; + +}; // namespace webf + +#endif // BRIDGE_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..055b438488 --- /dev/null +++ b/bridge/bindings/qjs/cppgc/garbage_collected.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_GARBAGE_COLLECTED_H +#define BRIDGE_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 webf { + +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 webf + +#endif // BRIDGE_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..47fd9e2c43 --- /dev/null +++ b/bridge/bindings/qjs/cppgc/gc_visitor.cc @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "gc_visitor.h" +#include "bindings/qjs/script_wrappable.h" + +namespace webf { + +void GCVisitor::Trace(JSValue value) { + JS_MarkValue(runtime_, value, markFunc_); +} + +} // namespace webf diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.h b/bridge/bindings/qjs/cppgc/gc_visitor.h new file mode 100644 index 0000000000..0e7516eddb --- /dev/null +++ b/bridge/bindings/qjs/cppgc/gc_visitor.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_GC_VISITOR_H +#define BRIDGE_GC_VISITOR_H + +#include +#include + +#include "foundation/macros.h" +#include "member.h" + +namespace webf { + +class ScriptWrappable; + +// Use GCVisitor to keep track gc managed members in C++ class. +class GCVisitor final { + WEBF_DISALLOW_NEW(); + WEBF_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 webf + +#endif // BRIDGE_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..7408d5b71b --- /dev/null +++ b/bridge/bindings/qjs/cppgc/local_handle.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ +#define BRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ + +#include +#include +#include "foundation/casting.h" +#include "foundation/macros.h" +#include "mutation_scope.h" + +namespace webf { + +template +class LocalTrait; +class ScriptWrappable; + +/** + * A stack allocated class which hold object reference temporary. + */ +template +class Local { + WEBF_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 webf + +#endif // BRIDGE_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..d0a516c35a --- /dev/null +++ b/bridge/bindings/qjs/cppgc/member.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ +#define BRIDGE_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 webf { + +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(const Member& other) { + raw_ = other.raw_; + runtime_ = other.runtime_; + } + ~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() const { + 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) { + raw_ = other.raw_; + runtime_ = other.runtime_; + 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; + } + + mutable T* raw_{nullptr}; + JSRuntime* runtime_{nullptr}; +}; + +} // namespace webf + +#endif // BRIDGE_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..5be04f98ff --- /dev/null +++ b/bridge/bindings/qjs/cppgc/mutation_scope.cc @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "mutation_scope.h" +#include "bindings/qjs/script_wrappable.h" +#include "core/executing_context.h" + +namespace webf { + +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 webf diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.h b/bridge/bindings/qjs/cppgc/mutation_scope.h new file mode 100644 index 0000000000..e48ec54688 --- /dev/null +++ b/bridge/bindings/qjs/cppgc/mutation_scope.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ +#define BRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ + +#include +#include +#include "foundation/macros.h" + +namespace webf { + +class ExecutingContext; +class ScriptWrappable; + +/** + * A stack-allocated class that record all members mutations in stack scope. + */ +class MemberMutationScope { + WEBF_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 webf + +#endif // BRIDGE_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..28427ebe61 --- /dev/null +++ b/bridge/bindings/qjs/dictionary_base.cc @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "dictionary_base.h" + +namespace webf { + +JSValue DictionaryBase::toQuickJS(JSContext* ctx) const { + JSValue object = JS_NewObject(ctx); + if (!FillQJSObjectWithMembers(ctx, object)) { + return JS_NULL; + } + return object; +} + +} // namespace webf diff --git a/bridge/bindings/qjs/dictionary_base.h b/bridge/bindings/qjs/dictionary_base.h new file mode 100644 index 0000000000..e51e50a0b6 --- /dev/null +++ b/bridge/bindings/qjs/dictionary_base.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ +#define BRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ + +#include "bindings/qjs/cppgc/garbage_collected.h" + +namespace webf { + +class ExceptionState; + +// 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; + virtual bool FillMembersWithQJSObject(JSContext* ctx, JSValue qjs_object, ExceptionState& exception_state) = 0; +}; + +} // namespace webf + +#endif // BRIDGE_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 bca4d6c89b..0000000000 --- a/bridge/bindings/qjs/dom/all_collection.cc +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "all_collection.h" - -namespace webf::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 webf::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 0eb5d28d15..0000000000 --- a/bridge/bindings/qjs/dom/all_collection.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_ALL_COLLECTION_H -#define BRIDGE_ALL_COLLECTION_H - -#include "bindings/qjs/host_object.h" -#include "node.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_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 d66731722c..0000000000 --- a/bridge/bindings/qjs/dom/comment_node.cc +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "comment_node.h" -#include "document.h" -#include "webf_bridge.h" - -namespace webf::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 webf::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 cdda4520c1..0000000000 --- a/bridge/bindings/qjs/dom/comment_node.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_COMMENT_NODE_H -#define BRIDGE_COMMENT_NODE_H - -#include "node.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_COMMENT_NODE_H diff --git a/bridge/bindings/qjs/dom/custom_event.cc b/bridge/bindings/qjs/dom/custom_event.cc deleted file mode 100644 index e0b319d4d8..0000000000 --- a/bridge/bindings/qjs/dom/custom_event.cc +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "custom_event.h" -#include "bindings/qjs/qjs_patch.h" -#include "webf_bridge.h" - -#include - -namespace webf::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 webf::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 56f9f0f14a..0000000000 --- a/bridge/bindings/qjs/dom/custom_event.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_CUSTOM_EVENT_H -#define BRIDGE_CUSTOM_EVENT_H - -#include "event.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_CUSTOM_EVENT_H diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/bindings/qjs/dom/document.cc deleted file mode 100644 index 14cb478976..0000000000 --- a/bridge/bindings/qjs/dom/document.cc +++ /dev/null @@ -1,641 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF 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 webf::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; -} - -JSValue Document::querySelector(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'querySelector' on 'Document': 1 argument required, but only 0 present."); - } - - getDartMethod()->flushUICommand(); - - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - std::string selectorText = jsValueToStdString(ctx, argv[0]); - NativeValue arguments[] = {Native_NewCString(selectorText)}; - - return document->invokeBindingMethod("querySelector", 1, arguments); -} - -JSValue Document::querySelectorAll(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Uncaught TypeError: Failed to execute 'querySelector' on 'Document': 1 argument required, but only 0 present."); - } - - getDartMethod()->flushUICommand(); - - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - std::string selectorText = jsValueToStdString(ctx, argv[0]); - NativeValue arguments[] = {Native_NewCString(selectorText)}; - - return document->invokeBindingMethod("querySelectorAll", 1, arguments); -} - -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 webf::binding::qjs diff --git a/bridge/bindings/qjs/dom/document.h b/bridge/bindings/qjs/dom/document.h deleted file mode 100644 index 6d6cfb4be1..0000000000 --- a/bridge/bindings/qjs/dom/document.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_DOCUMENT_H -#define BRIDGE_DOCUMENT_H - -#include "element.h" -#include "frame_request_callback_collection.h" -#include "node.h" -#include "script_animation_controller.h" - -namespace webf::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); - static JSValue querySelector(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue querySelectorAll(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); - DEFINE_PROTOTYPE_FUNCTION(querySelector, 1); - DEFINE_PROTOTYPE_FUNCTION(querySelectorAll, 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 webf::binding::qjs - -#endif // BRIDGE_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 6d898a91ac..0000000000 --- a/bridge/bindings/qjs/dom/document_fragment.cc +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "document_fragment.h" -#include "document.h" -#include "webf_bridge.h" - -namespace webf::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 webf::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 5356000546..0000000000 --- a/bridge/bindings/qjs/dom/document_fragment.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_DOCUMENT_FRAGMENT_H -#define BRIDGE_DOCUMENT_FRAGMENT_H - -#include "node.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_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 841c12deaa..0000000000 --- a/bridge/bindings/qjs/dom/document_test.cc +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "event_target.h" -#include "gtest/gtest.h" -#include "page.h" -#include "webf_test_env.h" - -TEST(Document, createTextNode) { - bool static errorCalled = false; - bool static logCalled = false; - webf::WebFPage::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) { - WEBF_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; - webf::WebFPage::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) { - WEBF_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) { - webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; - - webf::WebFPage* 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 cf3533d354..0000000000 --- a/bridge/bindings/qjs/dom/element.cc +++ /dev/null @@ -1,901 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF 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 "webf_test_env.h" -#endif - -namespace webf::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 webf 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 webf::binding::qjs diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/bindings/qjs/dom/element.h deleted file mode 100644 index cb7c64a9c7..0000000000 --- a/bridge/bindings/qjs/dom/element.h +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_ELEMENT_H -#define BRIDGE_ELEMENT_H - -#include -#include "bindings/qjs/garbage_collected.h" -#include "bindings/qjs/host_object.h" -#include "node.h" -#include "style_declaration.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_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/anchor_element.d.ts b/bridge/bindings/qjs/dom/elements/anchor_element.d.ts deleted file mode 100644 index 95a3a5b239..0000000000 --- a/bridge/bindings/qjs/dom/elements/anchor_element.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -interface HostObject {} -interface Element {} - -interface AnchorElement extends Element { - href: string; - target: string; - accessKey: string; - hash: string; - host: string; - hostname: string; - port: string; - readonly origin: string; - password: string; - pathname: string; - protocol: string; -} diff --git a/bridge/bindings/qjs/dom/elements/canvas_element.d.ts b/bridge/bindings/qjs/dom/elements/canvas_element.d.ts deleted file mode 100644 index dcb0cd8083..0000000000 --- a/bridge/bindings/qjs/dom/elements/canvas_element.d.ts +++ /dev/null @@ -1,57 +0,0 @@ -type int64 = number; -type double = number; - -interface HostObject {} -interface Element {} - -interface CanvasRenderingContext2D extends HostObject { - fillStyle: string; - direction: string; - font: string; - strokeStyle: string; - lineCap: string; - lineDashOffset: double; - lineJoin: string; - lineWidth: double; - miterLimit: double; - textAlign: string; - textBaseline: string; - // @TODO: Following number should be double. - // Reference https://html.spec.whatwg.org/multipage/canvas.html - arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void; - arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void; - beginPath(): void; - bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): void; - 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; - 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; - fillText(text: string, x: number, y: number, maxWidth?: number): void; - lineTo(x: number, y: number): void; - moveTo(x: number, y: number): void; - rect(x: number, y: number, w: number, h: number): void; - restore(): void; - resetTransform(): void; - rotate(angle: number): void; - quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): void; - stroke(): void; - strokeRect(x: number, y: number, w: number, h: number): void; - save(): void; - scale(x: number, y: number): void; - strokeText(text: string, x: number, y: number, maxWidth?: number): void; - setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void; - transform(a: number, b: number, c: number, d: number, e: number, f: number): void; - translate(x: number, y: number): void; - reset(): void; -} - -interface CanvasElement extends Element { - width: int64; - height: int64; - getContext: (contextType: string) => CanvasRenderingContext2D; -} 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 16ac11a664..0000000000 --- a/bridge/bindings/qjs/dom/elements/image_element.cc +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "image_element.h" -#include "bindings/qjs/qjs_patch.h" -#include "page.h" - -namespace webf::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 webf::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 4a3f0130c3..0000000000 --- a/bridge/bindings/qjs/dom/elements/image_element.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_IMAGE_ELEMENT_H -#define BRIDGE_IMAGE_ELEMENT_H - -#include "bindings/qjs/dom/element.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_IMAGE_ELEMENTT_H diff --git a/bridge/bindings/qjs/dom/elements/input_element.d.ts b/bridge/bindings/qjs/dom/elements/input_element.d.ts deleted file mode 100644 index 09484b99b6..0000000000 --- a/bridge/bindings/qjs/dom/elements/input_element.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -interface HostObject {} -interface Element {} - -interface InputElement extends Element { - width: number; - height: number; - defaultValue: string; - value: string; - accept: string; - autocomplete: string; - autofocus: boolean; - checked: boolean; - disabled: boolean; - min: string; - max: string; - minLength: long; - maxLength: long; - size: long; - multiple: boolean; - name: string; - step: string; - pattern: string; - required: boolean; - readonly: boolean; - placeholder: string - type: string; - inputMode: string; - focus(): void; - blur(): void; -} 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 0fab56dae8..0000000000 --- a/bridge/bindings/qjs/dom/elements/template_element.cc +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF 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 webf::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 webf::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 8bc4bacce1..0000000000 --- a/bridge/bindings/qjs/dom/elements/template_element.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_TEMPLATE_ELEMENT_H -#define BRIDGE_TEMPLATE_ELEMENT_H - -#include "bindings/qjs/dom/document_fragment.h" -#include "bindings/qjs/dom/element.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_TEMPLATE_ELEMENTT_H diff --git a/bridge/bindings/qjs/dom/elements/textarea_element.d.ts b/bridge/bindings/qjs/dom/elements/textarea_element.d.ts deleted file mode 100644 index a318a5df47..0000000000 --- a/bridge/bindings/qjs/dom/elements/textarea_element.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -interface HostObject {} -interface Element {} - -// https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element -interface TextareaElement extends Element { - defaultValue: string; - value: string; - cols: long; - rows: long; - wrap: string; - autofocus: boolean; - autocomplete: string; - disabled: boolean; - minLength: long; - maxLength: long; - name: string; - placeholder: string; - readonly: boolean; - required: boolean; - inputMode: string; - focus(): void; - blur(): void; -} diff --git a/bridge/bindings/qjs/dom/event.cc b/bridge/bindings/qjs/dom/event.cc deleted file mode 100644 index db2a70212d..0000000000 --- a/bridge/bindings/qjs/dom/event.cc +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF 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 "webf_bridge.h" - -namespace webf::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 webf::binding::qjs diff --git a/bridge/bindings/qjs/dom/event.h b/bridge/bindings/qjs/dom/event.h deleted file mode 100644 index cab749cc2c..0000000000 --- a/bridge/bindings/qjs/dom/event.h +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_EVENT_H -#define BRIDGE_EVENT_H - -#include "bindings/qjs/host_class.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_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 ba77834667..0000000000 --- a/bridge/bindings/qjs/dom/event_listener_map.cc +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "event_listener_map.h" - -namespace webf::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 webf::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 d04901873d..0000000000 --- a/bridge/bindings/qjs/dom/event_listener_map.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ -#define BRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ - -#include -#include -#include "include/webf_foundation.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_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 f6ab495d4e..0000000000 --- a/bridge/bindings/qjs/dom/event_target.cc +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF 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 "webf_bridge.h" - -#define PROPAGATION_STOPPED 1 -#define PROPAGATION_CONTINUE 0 - -#if UNIT_TEST -#include "webf_test_env.h" -#endif - -namespace webf::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); - std::string eventType = jsValueToStdString(ctx, atomString); - // There are still one reference_count in atom. It's safe to free here. - JS_FreeValue(ctx, atomString); - - if (eventType[0] == 'o' && eventType[1] == 'n') { - if (EventTypeNames::isEventTypeName(eventType)) { - return true; - } - - return !JS_IsNull(eventTarget->getAttributesEventHandler(atomString)); - } - - 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); - std::string eventType = jsValueToStdString(ctx, atomString); - // There are still one reference_count in atom. It's safe to free here. - JS_FreeValue(ctx, atomString); - - if (eventType[0] == 'o' && eventType[1] == 'n') { - return eventTarget->getAttributesEventHandler(atomString); - } - - 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); - std::string eventType = jsValueToStdString(ctx, atomString); - - if (eventType.substr(0, 2) == "on") { - eventTarget->setAttributesEventHandler(atomString, value); - } else { - eventTarget->m_properties.setProperty(JS_DupAtom(ctx, atom), JS_DupValue(ctx, value)); - if (isJavaScriptExtensionElementInstance(eventTarget->context(), eventTarget->jsObject) && eventType[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(JSValue key, JSValue value) { - std::string eventType = jsValueToStdString(m_ctx, key).substr(2); - JSAtom atom = JS_NewAtom(m_ctx, eventType.c_str()); - - 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(JSValue key) { - std::string eventType = jsValueToStdString(m_ctx, key).substr(2); - JSAtom atom = JS_NewAtom(m_ctx, eventType.c_str()); - 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 webf::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 e85346a299..0000000000 --- a/bridge/bindings/qjs/dom/event_target.h +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_EVENT_TARGET_H -#define BRIDGE_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 webf::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(JSValue key, JSValue value); - JSValue getAttributesEventHandler(JSValue p); - - private: - bool internalDispatchEvent(EventInstance* eventInstance); - static void finalize(JSRuntime* rt, JSValue val); - friend EventTarget; - friend StyleDeclarationInstance; -}; - -} // namespace webf::binding::qjs - -#endif // BRIDGE_EVENT_TARGET_H 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 7d9e099e30..0000000000 --- a/bridge/bindings/qjs/dom/event_type_names.cc +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "event_type_names.h" -#include -#include - -namespace webf::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 webf::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 9a55ebb9f2..0000000000 --- a/bridge/bindings/qjs/dom/event_type_names.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_BINDINGS_QJS_DOM_EVENT_TYPE_NAMES_H_ -#define BRIDGE_BINDINGS_QJS_DOM_EVENT_TYPE_NAMES_H_ - -#include -#include - -namespace webf::binding::qjs { -class EventTypeNames { - public: - static bool isEventTypeName(const std::string& name); -}; - -} // namespace webf::binding::qjs - -#endif // BRIDGE_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/close_event.d.ts b/bridge/bindings/qjs/dom/events/close_event.d.ts deleted file mode 100644 index 0e5ae36fb1..0000000000 --- a/bridge/bindings/qjs/dom/events/close_event.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -interface Event {} -type int64 = number; - -interface CloseEvent extends Event { - readonly code: int64; - readonly reason: string; - readonly wasClean: boolean; -} diff --git a/bridge/bindings/qjs/dom/events/input_event.d.ts b/bridge/bindings/qjs/dom/events/input_event.d.ts deleted file mode 100644 index e0693e9bd6..0000000000 --- a/bridge/bindings/qjs/dom/events/input_event.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -interface Event {} - -interface InputEvent extends Event { - readonly inputType: string; - readonly data: string; -} diff --git a/bridge/bindings/qjs/dom/events/intersection_change.d.ts b/bridge/bindings/qjs/dom/events/intersection_change.d.ts deleted file mode 100644 index 0e42d5e1e2..0000000000 --- a/bridge/bindings/qjs/dom/events/intersection_change.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -interface Event {} - -interface IntersectionChangeEvent extends Event { - readonly intersectionRatio: number; -} 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/events/touch_event.cc b/bridge/bindings/qjs/dom/events/touch_event.cc deleted file mode 100644 index 247fcd0596..0000000000 --- a/bridge/bindings/qjs/dom/events/touch_event.cc +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "touch_event.h" -#include "bindings/qjs/qjs_patch.h" -#include "page.h" - -namespace webf::binding::qjs { - -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) {} - -JSValue TouchList::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { - std::string key = jsAtomToStdString(ctx, atom); - if (isNumberIndex(key)) { - size_t index = std::stoi(key); - return (new Touch(m_context, m_touches[index]))->jsObject; - } - - return JS_NULL; -} - -int TouchList::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { - return 0; -} - -IMPL_PROPERTY_GETTER(TouchList, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* touchList = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostExoticObjectClassId)); - return JS_NewUint32(ctx, touchList->_length); -} -IMPL_PROPERTY_SETTER(TouchList, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} - -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(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewUint32(ctx, object->m_nativeTouch->identifier); -} -IMPL_PROPERTY_GETTER(Touch, target)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - auto* eventTarget = object->m_nativeTouch->target; - return JS_DupValue(ctx, eventTarget->instance->jsObject); -} -IMPL_PROPERTY_GETTER(Touch, clientX)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, object->m_nativeTouch->clientX); -} -IMPL_PROPERTY_GETTER(Touch, clientY)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, object->m_nativeTouch->clientY); -} -IMPL_PROPERTY_GETTER(Touch, screenX)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, object->m_nativeTouch->screenX); -} -IMPL_PROPERTY_GETTER(Touch, screenY)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, object->m_nativeTouch->screenY); -} -IMPL_PROPERTY_GETTER(Touch, pageX)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, object->m_nativeTouch->pageX); -} -IMPL_PROPERTY_GETTER(Touch, pageY)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, object->m_nativeTouch->pageY); -} -IMPL_PROPERTY_GETTER(Touch, radiusX)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, object->m_nativeTouch->radiusX); -} -IMPL_PROPERTY_GETTER(Touch, radiusY)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, object->m_nativeTouch->radiusY); -} -IMPL_PROPERTY_GETTER(Touch, rotationAngle)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, object->m_nativeTouch->rotationAngle); -} -IMPL_PROPERTY_GETTER(Touch, force)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, object->m_nativeTouch->force); -} -IMPL_PROPERTY_GETTER(Touch, altitudeAngle)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, object->m_nativeTouch->altitudeAngle); -} -IMPL_PROPERTY_GETTER(Touch, azimuthAngle)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, object->m_nativeTouch->azimuthAngle); -} -IMPL_PROPERTY_GETTER(Touch, touchType)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewUint32(ctx, object->m_nativeTouch->touchType); -} - -TouchEvent::TouchEvent(ExecutionContext* context) : Event(context) {} - -JSValue TouchEvent::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to construct 'TouchEvent': 1 argument required, but only 0 present."); - } - - JSValue eventTypeValue = argv[0]; - JSValue eventInit = JS_NULL; - - if (argc == 2) { - eventInit = argv[1]; - } - - auto* nativeEvent = new NativeTouchEvent(); -#if ANDROID_32_BIT - nativeEvent->nativeEvent.type = reinterpret_cast(jsValueToNativeString(ctx, eventTypeValue).release()); -#else - nativeEvent->nativeEvent.type = jsValueToNativeString(ctx, eventTypeValue).release(); -#endif - - if (JS_IsObject(eventInit)) { - JSAtom touchesAtom = JS_NewAtom(m_ctx, "touches"); - JSAtom targetTouchesAtom = JS_NewAtom(m_ctx, "targetTouches"); - JSAtom changedTouchesAtom = JS_NewAtom(m_ctx, "changedTouches"); - JSAtom altKeyAtom = JS_NewAtom(m_ctx, "altKey"); - JSAtom metaKeyAtom = JS_NewAtom(m_ctx, "metaKey"); - JSAtom ctrlKeyAtom = JS_NewAtom(m_ctx, "ctrlKey"); - JSAtom shiftKeyAtom = JS_NewAtom(m_ctx, "shiftKey"); - auto* ne = reinterpret_cast(nativeEvent); - - if (JS_HasProperty(m_ctx, eventInit, touchesAtom)) { - JSValue touchesValue = JS_GetProperty(ctx, eventInit, touchesAtom); - if (JS_IsArray(ctx, touchesValue)) { - uint32_t length; - JSValue lengthValue = JS_GetPropertyStr(ctx, touchesValue, "length"); - JS_ToUint32(ctx, &length, lengthValue); - - ne->touches = new NativeTouch*[length]; - ne->touchLength = length; - for (int i = 0; i < length; i++) { - JSValue v = JS_GetPropertyUint32(ctx, touchesValue, i); - if (JS_IsInstanceOf(ctx, v, TouchEvent::instance(m_context)->jsObject)) { - ne->touches[i] = static_cast(JS_GetOpaque(v, ExecutionContext::kHostObjectClassId)); - } - } - } - } - if (JS_HasProperty(m_ctx, eventInit, targetTouchesAtom)) { - JSValue targetTouchesValue = JS_GetProperty(ctx, eventInit, targetTouchesAtom); - if (JS_IsArray(ctx, targetTouchesValue)) { - uint32_t length; - JSValue lengthValue = JS_GetPropertyStr(ctx, targetTouchesValue, "length"); - JS_ToUint32(ctx, &length, lengthValue); - - ne->targetTouches = new NativeTouch*[length]; - ne->targetTouchesLength = length; - for (int i = 0; i < length; i++) { - JSValue v = JS_GetPropertyUint32(ctx, targetTouchesValue, i); - if (JS_IsInstanceOf(ctx, v, TouchEvent::instance(m_context)->jsObject)) { - ne->targetTouches[i] = static_cast(JS_GetOpaque(v, ExecutionContext::kHostObjectClassId)); - } - } - } - } - if (JS_HasProperty(m_ctx, eventInit, changedTouchesAtom)) { - JSValue changedTouchesValue = JS_GetProperty(ctx, eventInit, changedTouchesAtom); - if (JS_IsArray(ctx, changedTouchesValue)) { - uint32_t length; - JSValue lengthValue = JS_GetPropertyStr(ctx, changedTouchesValue, "length"); - JS_ToUint32(ctx, &length, lengthValue); - - ne->changedTouches = new NativeTouch*[length]; - ne->changedTouchesLength = length; - for (int i = 0; i < length; i++) { - JSValue v = JS_GetPropertyUint32(ctx, changedTouchesValue, i); - if (JS_IsInstanceOf(ctx, v, TouchEvent::instance(m_context)->jsObject)) { - ne->changedTouches[i] = static_cast(JS_GetOpaque(v, ExecutionContext::kHostObjectClassId)); - } - } - } - } - if (JS_HasProperty(m_ctx, eventInit, altKeyAtom)) { - ne->altKey = JS_ToBool(m_ctx, JS_GetProperty(m_ctx, eventInit, altKeyAtom)) ? 1 : 0; - } - if (JS_HasProperty(m_ctx, eventInit, metaKeyAtom)) { - ne->metaKey = JS_ToBool(m_ctx, JS_GetProperty(m_ctx, eventInit, metaKeyAtom)) ? 1 : 0; - } - if (JS_HasProperty(m_ctx, eventInit, ctrlKeyAtom)) { - ne->ctrlKey = JS_ToBool(m_ctx, JS_GetProperty(m_ctx, eventInit, ctrlKeyAtom)) ? 1 : 0; - } - if (JS_HasProperty(m_ctx, eventInit, shiftKeyAtom)) { - ne->shiftKey = JS_ToBool(m_ctx, JS_GetProperty(m_ctx, eventInit, shiftKeyAtom)) ? 1 : 0; - } - - JS_FreeAtom(m_ctx, touchesAtom); - JS_FreeAtom(m_ctx, targetTouchesAtom); - JS_FreeAtom(m_ctx, changedTouchesAtom); - JS_FreeAtom(m_ctx, altKeyAtom); - JS_FreeAtom(m_ctx, metaKeyAtom); - JS_FreeAtom(m_ctx, ctrlKeyAtom); - JS_FreeAtom(m_ctx, shiftKeyAtom); - } - - auto event = new TouchEventInstance(this, reinterpret_cast(nativeEvent)); - return event->jsObject; -} -IMPL_PROPERTY_GETTER(TouchEvent, touches)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast(event->nativeEvent); - auto* touchList = new TouchList(event->m_context, nativeEvent->touches, nativeEvent->touchLength); - return touchList->jsObject; -} - -IMPL_PROPERTY_GETTER(TouchEvent, targetTouches)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast(event->nativeEvent); - auto* targetTouchList = new TouchList(event->m_context, nativeEvent->targetTouches, nativeEvent->targetTouchesLength); - return targetTouchList->jsObject; -} - -IMPL_PROPERTY_GETTER(TouchEvent, changedTouches)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast(event->nativeEvent); - auto* changedTouchList = new TouchList(event->m_context, nativeEvent->changedTouches, nativeEvent->changedTouchesLength); - return changedTouchList->jsObject; -} - -IMPL_PROPERTY_GETTER(TouchEvent, altKey)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast(event->nativeEvent); - return JS_NewBool(ctx, nativeEvent->altKey ? 1 : 0); -} - -IMPL_PROPERTY_GETTER(TouchEvent, metaKey)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast(event->nativeEvent); - return JS_NewBool(ctx, nativeEvent->metaKey ? 1 : 0); -} - -IMPL_PROPERTY_GETTER(TouchEvent, ctrlKey)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast(event->nativeEvent); - return JS_NewBool(ctx, nativeEvent->ctrlKey ? 1 : 0); -} - -IMPL_PROPERTY_GETTER(TouchEvent, shiftKey)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast(event->nativeEvent); - return JS_NewBool(ctx, nativeEvent->shiftKey ? 1 : 0); -} - -TouchEventInstance::TouchEventInstance(TouchEvent* event, NativeEvent* nativeEvent) : EventInstance(event, nativeEvent) {} - -} // namespace webf::binding::qjs diff --git a/bridge/bindings/qjs/dom/events/touch_event.h b/bridge/bindings/qjs/dom/events/touch_event.h deleted file mode 100644 index 92ded406c1..0000000000 --- a/bridge/bindings/qjs/dom/events/touch_event.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_TOUCH_EVENT_H -#define BRIDGE_TOUCH_EVENT_H - -#include "bindings/qjs/dom/element.h" - -namespace webf::binding::qjs { - -void bindTouchEvent(ExecutionContext* context); - -struct NativeTouch { - int64_t identifier; - NativeEventTarget* target; - double clientX; - double clientY; - double screenX; - double screenY; - double pageX; - double pageY; - double radiusX; - double radiusY; - double rotationAngle; - double force; - double altitudeAngle; - double azimuthAngle; - int64_t touchType; -}; - -class Touch : public HostObject { - public: - Touch() = delete; - explicit Touch(ExecutionContext* context, NativeTouch* nativePtr); - - private: - NativeTouch* m_nativeTouch{nullptr}; - DEFINE_READONLY_PROPERTY(identifier); - DEFINE_READONLY_PROPERTY(target); - DEFINE_READONLY_PROPERTY(clientX); - DEFINE_READONLY_PROPERTY(clientY); - DEFINE_READONLY_PROPERTY(screenX); - DEFINE_READONLY_PROPERTY(screenY); - DEFINE_READONLY_PROPERTY(pageX); - DEFINE_READONLY_PROPERTY(pageY); - DEFINE_READONLY_PROPERTY(radiusX); - DEFINE_READONLY_PROPERTY(radiusY); - DEFINE_READONLY_PROPERTY(rotationAngle); - DEFINE_READONLY_PROPERTY(force); - DEFINE_READONLY_PROPERTY(altitudeAngle); - DEFINE_READONLY_PROPERTY(azimuthAngle); - DEFINE_READONLY_PROPERTY(touchType); -}; - -class TouchList : public ExoticHostObject { - public: - TouchList() = delete; - explicit TouchList(ExecutionContext* context, NativeTouch** touches, int64_t length); - - 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: - DEFINE_PROPERTY(length); - NativeTouch** m_touches{nullptr}; - int64_t _length; -}; - -struct NativeTouchEvent { - NativeEvent nativeEvent; - - NativeTouch** touches; - int64_t touchLength; - NativeTouch** targetTouches; - int64_t targetTouchesLength; - NativeTouch** changedTouches; - int64_t changedTouchesLength; - - int64_t altKey; - int64_t metaKey; - int64_t ctrlKey; - int64_t shiftKey; -}; -class TouchEventInstance; -class TouchEvent : public Event { - public: - TouchEvent() = delete; - explicit TouchEvent(ExecutionContext* context); - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - - OBJECT_INSTANCE(TouchEvent); - - private: - DEFINE_PROTOTYPE_READONLY_PROPERTY(touches); - DEFINE_PROTOTYPE_READONLY_PROPERTY(targetTouches); - DEFINE_PROTOTYPE_READONLY_PROPERTY(changedTouches); - DEFINE_PROTOTYPE_READONLY_PROPERTY(altKey); - DEFINE_PROTOTYPE_READONLY_PROPERTY(metaKey); - DEFINE_PROTOTYPE_READONLY_PROPERTY(ctrlKey); - DEFINE_PROTOTYPE_READONLY_PROPERTY(shiftKey); - - friend TouchEventInstance; -}; - -class TouchEventInstance : public EventInstance { - public: - TouchEventInstance() = delete; - explicit TouchEventInstance(TouchEvent* event, NativeEvent* nativeEvent); - - private: - friend TouchEvent; -}; - -} // namespace webf::binding::qjs - -#endif // BRIDGE_TOUCH_EVENTT_H 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 e7cc6b1d10..0000000000 --- a/bridge/bindings/qjs/dom/frame_request_callback_collection.cc +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "frame_request_callback_collection.h" - -namespace webf::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 webf::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 46aa42bc00..0000000000 --- a/bridge/bindings/qjs/dom/frame_request_callback_collection.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ -#define BRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ - -#include "bindings/qjs/executing_context.h" - -namespace webf::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 webf::binding::qjs - -class frame_request_callback_collection {}; - -#endif // BRIDGE_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 d043f0c35f..0000000000 --- a/bridge/bindings/qjs/dom/node.cc +++ /dev/null @@ -1,577 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF 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 "text_node.h" -#include "webf_bridge.h" - -namespace webf::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 webf::binding::qjs diff --git a/bridge/bindings/qjs/dom/node.h b/bridge/bindings/qjs/dom/node.h deleted file mode 100644 index d8b96dea5f..0000000000 --- a/bridge/bindings/qjs/dom/node.h +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_NODE_H -#define BRIDGE_NODE_H - -#include -#include - -#include "event_target.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_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 25e1c259bb..0000000000 --- a/bridge/bindings/qjs/dom/script_animation_controller.cc +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "script_animation_controller.h" -#include "dart_methods.h" -#include "frame_request_callback_collection.h" - -#if UNIT_TEST -#include "webf_test_env.h" -#endif - -namespace webf::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 webf::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 19b37b9c6c..0000000000 --- a/bridge/bindings/qjs/dom/script_animation_controller.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ -#define BRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ - -#include "bindings/qjs/garbage_collected.h" -#include "frame_request_callback_collection.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_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 deleted file mode 100644 index 68f4ef6c76..0000000000 --- a/bridge/bindings/qjs/dom/style_declaration.cc +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "style_declaration.h" -#include "bindings/qjs/dom/css_property_list.h" -#include "event_target.h" -#include "webf_bridge.h" - -namespace webf::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 webf::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 1e2b5f36cc..0000000000 --- a/bridge/bindings/qjs/dom/style_declaration.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_STYLE_DECLARATION_H -#define BRIDGE_STYLE_DECLARATION_H - -#include "bindings/qjs/host_class.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_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 777f866ad7..0000000000 --- a/bridge/bindings/qjs/dom/text_node.cc +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "text_node.h" -#include "document.h" -#include "webf_bridge.h" - -namespace webf::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 webf::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 ca94284d79..0000000000 --- a/bridge/bindings/qjs/dom/text_node.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_TEXT_NODE_H -#define BRIDGE_TEXT_NODE_H - -#include "node.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_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..6ba169d568 --- /dev/null +++ b/bridge/bindings/qjs/exception_message.cc @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "exception_message.h" +#include +#include +#include + +namespace webf { + +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 webf diff --git a/bridge/bindings/qjs/exception_message.h b/bridge/bindings/qjs/exception_message.h new file mode 100644 index 0000000000..757887368e --- /dev/null +++ b/bridge/bindings/qjs/exception_message.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ +#define BRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ + +#include + +namespace webf { + +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 webf + +#endif // BRIDGE_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..a6262cfc9f --- /dev/null +++ b/bridge/bindings/qjs/exception_state.cc @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "exception_state.h" + +namespace webf { + +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 webf diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h new file mode 100644 index 0000000000..7369b1a9d0 --- /dev/null +++ b/bridge/bindings/qjs/exception_state.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_EXCEPTION_STATE_H +#define BRIDGE_EXCEPTION_STATE_H + +#include +#include +#include "foundation/macros.h" + +#define ASSERT_NO_EXCEPTION() ExceptionState().ReturnThis() + +namespace webf { + +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. + WEBF_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 webf + +#endif // BRIDGE_EXCEPTION_STATE_H diff --git a/bridge/bindings/qjs/executing_context.cc b/bridge/bindings/qjs/executing_context.cc deleted file mode 100644 index 33a3f95008..0000000000 --- a/bridge/bindings/qjs/executing_context.cc +++ /dev/null @@ -1,561 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF 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 "qjs_patch.h" -#include "webf_bridge.h" - -namespace webf::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 webf::binding::qjs diff --git a/bridge/bindings/qjs/executing_context.h b/bridge/bindings/qjs/executing_context.h deleted file mode 100644 index bfc5fba894..0000000000 --- a/bridge/bindings/qjs/executing_context.h +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_JS_CONTEXT_H -#define BRIDGE_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 "qjs_patch.h" -#include "rejected_promises.h" -#include "webf_foundation.h" - -using JSExceptionHandler = std::function; - -namespace webf::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 webf. -// 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 { - 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 { - 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 webf::binding::qjs - -#endif // BRIDGE_JS_CONTEXT_H diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h deleted file mode 100644 index 01fcb1b45f..0000000000 --- a/bridge/bindings/qjs/garbage_collected.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_GARBAGE_COLLECTED_H -#define BRIDGE_GARBAGE_COLLECTED_H - -#include -#include "include/webf_foundation.h" -#include "qjs_patch.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_GARBAGE_COLLECTED_H diff --git a/bridge/bindings/qjs/generated_code_helper.h b/bridge/bindings/qjs/generated_code_helper.h new file mode 100644 index 0000000000..50a4aa554e --- /dev/null +++ b/bridge/bindings/qjs/generated_code_helper.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_GENERATED_CODE_HELPER_H +#define BRIDGE_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 f7a64d0f9d..0baef2d3b7 100644 --- a/bridge/bindings/qjs/heap_hashmap.h +++ b/bridge/bindings/qjs/heap_hashmap.h @@ -6,98 +6,72 @@ #ifndef BRIDGE_BINDINGS_QJS_HEAP_HASHMAP_H_ #define BRIDGE_BINDINGS_QJS_HEAP_HASHMAP_H_ -#include #include +#include "cppgc/gc_visitor.h" -namespace webf::binding::qjs { +namespace webf { -template +template class HeapHashMap { public: - HeapHashMap() = delete; - explicit HeapHashMap(JSContext* ctx); + HeapHashMap(); ~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); + V GetProperty(K key); + void SetProperty(K key, V value); + void CopyWith(HeapHashMap* newValue); + void Erase(K key); - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const; + void Trace(GCVisitor* visitor) const; private: - JSRuntime* m_runtime{nullptr}; - JSContext* m_ctx{nullptr}; - std::unordered_map m_entries; + std::unordered_map entries_; }; -template -HeapHashMap::HeapHashMap(JSContext* ctx) : m_runtime(JS_GetRuntime(ctx)), m_ctx(ctx) {} +template +HeapHashMap::HeapHashMap() {} -template -HeapHashMap::~HeapHashMap() { - for (auto& entry : m_entries) { - JS_FreeAtomRT(m_runtime, entry.first); - JS_FreeValueRT(m_runtime, entry.second); - } -} -template -bool HeapHashMap::contains(K key) { - return m_entries.count(key) > 0; +template +HeapHashMap::~HeapHashMap() {} + +template +bool HeapHashMap::Contains(K key) { + return entries_.count(key) > 0; } -template -JSValue HeapHashMap::getProperty(K key) { - if (m_entries.count(key) == 0) +template +V 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) { - // 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]); - } - - m_entries[key] = value; +template +void HeapHashMap::SetProperty(K key, V value) { + entries_[key] = value; } -template -void HeapHashMap::copyWith(HeapHashMap* newValue) { - for (auto& entry : m_entries) { - // We should also dup atom if K is JSAtom. - if (std::is_same::value) { - JS_DupAtom(m_ctx, entry.first); - } - - newValue->m_entries[entry.first] = JS_DupValue(m_ctx, entry.second); - } +template +void HeapHashMap::CopyWith(HeapHashMap* newValue) { + newValue->entries_ = entries_; } -template -void HeapHashMap::erase(K key) { - if (m_entries.count(key) == 0) +template +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_FreeValueRT(m_runtime, m_entries[key]); - m_entries.erase(key); + entries_.erase(key); } -template -void HeapHashMap::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - for (auto& entry : m_entries) { - JS_MarkValue(rt, entry.second, mark_func); +template +void HeapHashMap::Trace(GCVisitor* visitor) const { + for (auto& entry : entries_) { + visitor->Trace(entry.second); } } -} // namespace webf::binding::qjs +} // namespace webf #endif // BRIDGE_BINDINGS_QJS_HEAP_HASHMAP_H_ diff --git a/bridge/bindings/qjs/heap_vector.h b/bridge/bindings/qjs/heap_vector.h new file mode 100644 index 0000000000..dda66171f7 --- /dev/null +++ b/bridge/bindings/qjs/heap_vector.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_ +#define BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_ + +namespace webf { + +template +class HeapVector final { + public: + HeapVector() = default; + + void Trace(GCVisitor* visitor) const; + + private: + std::vector entries_; +}; + +template +void HeapVector::Trace(GCVisitor* visitor) const { + for (auto& item : entries_) { + visitor->Trace(item); + } +} + +} // namespace webf + +#endif // BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_ diff --git a/bridge/bindings/qjs/host_class.h b/bridge/bindings/qjs/host_class.h index bcbf2290a8..e69de29bb2 100644 --- a/bridge/bindings/qjs/host_class.h +++ b/bridge/bindings/qjs/host_class.h @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_HOST_CLASS_H -#define BRIDGE_HOST_CLASS_H - -#include "executing_context.h" -#include "qjs_patch.h" -#include "quickjs/quickjs.h" - -namespace webf::binding::qjs { - -class Instance; - -class HostClass { - public: - 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 webf::binding::qjs - -#endif // BRIDGE_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 bfd1953a02..0000000000 --- a/bridge/bindings/qjs/host_class_test.cc +++ /dev/null @@ -1,512 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "host_class.h" -#include -#include "gtest/gtest.h" -#include "page.h" -#include "webf_test_env.h" - -namespace webf::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; - webf::WebFPage::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) { - WEBF_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; - webf::WebFPage::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; - WEBF_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; - webf::WebFPage::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; - WEBF_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; - webf::WebFPage::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; - WEBF_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; - webf::WebFPage::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; - WEBF_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; - WEBF_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) { - WEBF_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - webf::WebFPage::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) { - WEBF_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - webf::WebFPage::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; }); - webf::WebFPage::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, "__webf_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 webf::binding::qjs diff --git a/bridge/bindings/qjs/host_object.cc b/bridge/bindings/qjs/host_object.cc deleted file mode 100644 index a86db4f12c..0000000000 --- a/bridge/bindings/qjs/host_object.cc +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "host_object.h" - -namespace webf::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 webf::binding::qjs diff --git a/bridge/bindings/qjs/host_object.h b/bridge/bindings/qjs/host_object.h deleted file mode 100644 index 7e54d1943a..0000000000 --- a/bridge/bindings/qjs/host_object.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_HOST_OBJECT_H -#define BRIDGE_HOST_OBJECT_H - -#include "executing_context.h" - -namespace webf::binding::qjs { - -class HostObject { - public: - 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: - 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 webf::binding::qjs - -#endif // BRIDGE_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 66d0ffa1cf..0000000000 --- a/bridge/bindings/qjs/host_object_test.cc +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "host_object.h" -#include -#include "executing_context.h" -#include "page.h" -#include "webf_test_env.h" - -namespace webf::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; - webf::WebFPage::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; - webf::WebFPage::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) { - WEBF_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; - webf::WebFPage::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) { - WEBF_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; - webf::WebFPage::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) { - WEBF_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 webf::binding::qjs diff --git a/bridge/bindings/qjs/html_parser.cc b/bridge/bindings/qjs/html_parser.cc deleted file mode 100644 index 05dc5e61cd..0000000000 --- a/bridge/bindings/qjs/html_parser.cc +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF 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 webf::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 { - WEBF_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 webf::binding::qjs diff --git a/bridge/bindings/qjs/html_parser.h b/bridge/bindings/qjs/html_parser.h deleted file mode 100644 index 939a45f6b2..0000000000 --- a/bridge/bindings/qjs/html_parser.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_HTML_PARSER_H -#define BRIDGE_HTML_PARSER_H - -#include "bindings/qjs/dom/element.h" -#include "executing_context.h" -#include "include/webf_bridge.h" -#include "third_party/gumbo-parser/src/gumbo.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_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..6eb77b9c45 --- /dev/null +++ b/bridge/bindings/qjs/idl_type.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ +#define BRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ + +#include +#include "converter.h" + +namespace webf { + +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 {}; + +// Promise +struct IDLPromise : public IDLTypeBaseHelper {}; + +class JSEventHandler; +// EventHandler +struct IDLEventHandler : 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 webf + +#endif // BRIDGE_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..0f9b82237e --- /dev/null +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "js_based_event_listener.h" +#include "core/dom/events/event.h" + +namespace webf { + +// 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->IsContextValid()) + 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 webf 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..a5965e6a9e --- /dev/null +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ +#define BRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ + +#include +#include "core/dom/events/event_listener.h" +#include "core/executing_context.h" +#include "foundation/casting.h" + +namespace webf { + +// |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() = 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 webf + +#endif // BRIDGE_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..937e528619 --- /dev/null +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#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 webf { + +std::shared_ptr JSEventHandler::CreateOrNull(JSContext* ctx, + JSValue value, + JSEventHandler::HandlerType handler_type) { + if (!JS_IsFunction(ctx, value)) { + return nullptr; + } + + return std::make_shared(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. + if (event_handler_ == nullptr) + 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(event.ToValue()); + } + + ScriptValue result = event_handler_->Invoke(event.ctx(), event_target.ToValue(), 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 { + event_handler_->Trace(visitor); +} + +} // namespace webf diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h new file mode 100644 index 0000000000..981ca7ac2d --- /dev/null +++ b/bridge/bindings/qjs/js_event_handler.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ +#define BRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ + +#include "foundation/casting.h" +#include "js_based_event_listener.h" + +namespace webf { + +// |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::shared_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->GetListenerObject(); + } + return JS_NULL; + } + + explicit JSEventHandler(const std::shared_ptr& event_handler, HandlerType type) + : type_(type), event_handler_(event_handler){}; + + JSValue GetListenerObject() override { return event_handler_->ToQuickJS(); } + + // Helper functions for DowncastTraits. + bool IsEventHandler() 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 webf + +#endif // BRIDGE_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..4f2f9a46e1 --- /dev/null +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "js_event_listener.h" +#include "core/dom/events/event.h" + +#include +#include "core/dom/events/event_target.h" + +namespace webf { + +JSEventListener::JSEventListener(std::shared_ptr listener) : event_listener_(std::move(listener)) {} +JSValue JSEventListener::GetListenerObject() { + 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 webf diff --git a/bridge/bindings/qjs/js_event_listener.h b/bridge/bindings/qjs/js_event_listener.h new file mode 100644 index 0000000000..5346f90a54 --- /dev/null +++ b/bridge/bindings/qjs/js_event_listener.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ +#define BRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ + +#include "foundation/casting.h" +#include "js_based_event_listener.h" + +namespace webf { + +// |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() 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 webf + +#endif // BRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/js_context_macros.h b/bridge/bindings/qjs/macros.h similarity index 57% rename from bridge/bindings/qjs/js_context_macros.h rename to bridge/bindings/qjs/macros.h index fdc7859e33..0e1b33045f 100644 --- a/bridge/bindings/qjs/js_context_macros.h +++ b/bridge/bindings/qjs/macros.h @@ -3,16 +3,8 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#ifndef BRIDGE_JS_CONTEXT_MACROS_H -#define BRIDGE_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 BRIDGE_BINDING_MACROS_H +#define BRIDGE_BINDING_MACROS_H #define QJS_GLOBAL_BINDING_FUNCTION(context, function, name, argc) \ { \ @@ -23,40 +15,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 // BRIDGE_JS_CONTEXT_MACROS_H +#endif // BRIDGE_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..24b38a1ee7 --- /dev/null +++ b/bridge/bindings/qjs/member_installer.cc @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "member_installer.h" +#include +#include "core/executing_context.h" +#include "qjs_engine_patch.h" + +namespace webf { + +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 webf diff --git a/bridge/bindings/qjs/member_installer.h b/bridge/bindings/qjs/member_installer.h new file mode 100644 index 0000000000..4d98143720 --- /dev/null +++ b/bridge/bindings/qjs/member_installer.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_MEMBER_INSTALLER_H +#define BRIDGE_MEMBER_INSTALLER_H + +#include +#include + +namespace webf { + +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 webf + +#endif // BRIDGE_MEMBER_INSTALLER_H diff --git a/bridge/bindings/qjs/module_manager.cc b/bridge/bindings/qjs/module_manager.cc deleted file mode 100644 index 75b4672303..0000000000 --- a/bridge/bindings/qjs/module_manager.cc +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "module_manager.h" -#include "page.h" -#include "qjs_patch.h" - -namespace webf::binding::qjs { - -JSValue webFModuleListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute '__webf_module_listener__': 1 parameter required, but only 0 present."); - } - - JSValue callbackValue = argv[0]; - if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__webf_module_listener__': parameter 1 (callback) must be a function."); - } - - if (!JS_IsFunction(ctx, callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__webf_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 '__webf_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 webFInvokeModule(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc < 2) { - return JS_ThrowTypeError(ctx, "Failed to execute 'webf.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 '__webf_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 '__webf_flush_ui_command__': dart method (flushUICommand) is not registered."); - } - getDartMethod()->flushUICommand(); - return JS_NULL; -} - -void bindModuleManager(ExecutionContext* context) { - QJS_GLOBAL_BINDING_FUNCTION(context, webFModuleListener, "__webf_module_listener__", 1); - QJS_GLOBAL_BINDING_FUNCTION(context, webFInvokeModule, "__webf_invoke_module__", 3); - QJS_GLOBAL_BINDING_FUNCTION(context, flushUICommand, "__webf_flush_ui_command__", 0); -} - -} // namespace webf::binding::qjs diff --git a/bridge/bindings/qjs/module_manager.h b/bridge/bindings/qjs/module_manager.h deleted file mode 100644 index 814de1c0e8..0000000000 --- a/bridge/bindings/qjs/module_manager.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_MODULE_MANAGER_H -#define BRIDGE_MODULE_MANAGER_H - -#include "executing_context.h" - -namespace webf::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 webf::binding::qjs - -#endif // BRIDGE_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..f28dfb2df6 --- /dev/null +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "native_string_utils.h" +#include "bindings/qjs/qjs_engine_patch.h" + +namespace webf { + +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 webf diff --git a/bridge/bindings/qjs/native_string_utils.h b/bridge/bindings/qjs/native_string_utils.h new file mode 100644 index 0000000000..b69ad3742e --- /dev/null +++ b/bridge/bindings/qjs/native_string_utils.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_NATIVE_STRING_UTILS_H +#define BRIDGE_NATIVE_STRING_UTILS_H + +#include +#include +#include +#include +#include + +#include "foundation/native_string.h" + +namespace webf { + +// 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 webf + +#endif // BRIDGE_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 7647f92564..0000000000 --- a/bridge/bindings/qjs/native_value.cc +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF 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 "webf_bridge.h" - -namespace webf::binding::qjs { - -#define AnonymousFunctionCallPreFix "_anonymous_fn_" -#define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" - -NativeValue Native_NewNull() { - return (NativeValue){0, 0, NativeTag::TAG_NULL}; -} - -NativeValue Native_NewString(NativeString* string) { - return (NativeValue){ - reinterpret_cast(string), - 0, - 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){ - {.float64 = value}, - 0, - NativeTag::TAG_FLOAT64, - }; -} - -NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr) { - return (NativeValue){reinterpret_cast(ptr), static_cast(pointerType), NativeTag::TAG_POINTER}; -} - -NativeValue Native_NewBool(bool value) { - return (NativeValue){ - 0, - value ? 1 : 0, - NativeTag::TAG_BOOL, - }; -} - -NativeValue Native_NewInt32(int32_t value) { - return (NativeValue){ - 0, - 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){ - reinterpret_cast(string), - 0, - 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::NativeBindingObject, 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 = reinterpret_cast(value.u.uint64); - 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.int64); - } - case NativeTag::TAG_BOOL: { - return JS_NewBool(context->ctx(), value.int64 == 1); - } - case NativeTag::TAG_FLOAT64: { - return JS_NewFloat64(context->ctx(), value.u.float64); - } - case NativeTag::TAG_NULL: { - return JS_NULL; - } - case NativeTag::TAG_JSON: { - auto* str = reinterpret_cast(value.u.uint64); - JSValue returnedValue = JS_ParseJSON(context->ctx(), str, strlen(str), ""); - delete str; - return returnedValue; - } - case NativeTag::TAG_LIST: { - size_t len = value.int64; - auto* ptr = reinterpret_cast(value.u.uint64); - JSValue array = JS_NewArray(context->ctx()); - for (int i = 0; i < len; i++) { - NativeValue* native_value = ptr[i]; - JSValue newValue = nativeValueToJSValue(context, *native_value); - arrayPushValue(context->ctx(), array, newValue); - JS_FreeValue(context->ctx(), newValue); - } - delete ptr; - return array; - } - case NativeTag::TAG_POINTER: { - auto* ptr = reinterpret_cast(value.u.uint64); - int64_t ptrType = value.int64; - 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::NativeBindingObject)) { - auto* nativeEventTarget = static_cast(ptr); - return JS_DupValue(context->ctx(), nativeEventTarget->instance->jsObject); - } - } - case NativeTag::TAG_FUNCTION: { - int64_t functionId = value.int64; - return JS_NewCFunctionData(context->ctx(), anonymousFunction, 4, functionId, 0, nullptr); - } - case NativeTag::TAG_ASYNC_FUNCTION: { - int64_t functionId = value.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 webf::binding::qjs diff --git a/bridge/bindings/qjs/qjs_engine_patch.cc b/bridge/bindings/qjs/qjs_engine_patch.cc new file mode 100644 index 0000000000..9d91196bf2 --- /dev/null +++ b/bridge/bindings/qjs/qjs_engine_patch.cc @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "qjs_engine_patch.h" +#include +#include +#include + +typedef struct JSProxyData { + JSValue target; + JSValue handler; + uint8_t is_func; + uint8_t is_revoked; +} JSProxyData; + +typedef enum { + JS_GC_OBJ_TYPE_JS_OBJECT, + JS_GC_OBJ_TYPE_FUNCTION_BYTECODE, + JS_GC_OBJ_TYPE_SHAPE, + JS_GC_OBJ_TYPE_VAR_REF, + JS_GC_OBJ_TYPE_ASYNC_FUNCTION, + JS_GC_OBJ_TYPE_JS_CONTEXT, +} JSGCObjectTypeEnum; + +struct JSGCObjectHeader { + int ref_count; /* must come first, 32-bit */ + JSGCObjectTypeEnum gc_obj_type : 4; + uint8_t mark : 4; /* used by the GC */ + uint8_t dummy1; /* not used by the GC */ + uint16_t dummy2; /* not used by the GC */ + struct list_head link; +}; + +typedef struct JSShapeProperty { + uint32_t hash_next : 26; /* 0 if last in list */ + uint32_t flags : 6; /* JS_PROP_XXX */ + JSAtom atom; /* JS_ATOM_NULL = free property entry */ +} JSShapeProperty; + +struct JSShape { + /* hash table of size hash_mask + 1 before the start of the + structure (see prop_hash_end()). */ + JSGCObjectHeader header; + /* true if the shape is inserted in the shape hash table. If not, + JSShape.hash is not valid */ + uint8_t is_hashed; + /* If true, the shape may have small array index properties 'n' with 0 + <= n <= 2^31-1. If false, the shape is guaranteed not to have + small array index properties */ + uint8_t has_small_array_index; + uint32_t hash; /* current hash value */ + uint32_t prop_hash_mask; + int prop_size; /* allocated properties */ + int prop_count; /* include deleted properties */ + int deleted_prop_count; + JSShape* shape_hash_next; /* in JSRuntime.shape_hash[h] list */ + JSObject* proto; + JSShapeProperty prop[0]; /* prop_size elements */ +}; + +struct JSClass { + uint32_t class_id; /* 0 means free entry */ + JSAtom class_name; + JSClassFinalizer* finalizer; + JSClassGCMark* gc_mark; + JSClassCall* call; + /* pointers for exotic behavior, can be NULL if none are present */ + const JSClassExoticMethods* exotic; +}; + +struct JSRuntime { + JSMallocFunctions mf; + JSMallocState malloc_state; + const char* rt_info; + + int atom_hash_size; /* power of two */ + int atom_count; + int atom_size; + int atom_count_resize; /* resize hash table at this count */ + uint32_t* atom_hash; + JSString** atom_array; + int atom_free_index; /* 0 = none */ + + int class_count; /* size of class_array */ + JSClass* class_array; + + struct list_head context_list; /* list of JSContext.link */ + /* list of JSGCObjectHeader.link. List of allocated GC objects (used + by the garbage collector) */ + struct list_head gc_obj_list; + /* list of JSGCObjectHeader.link. Used during JS_FreeValueRT() */ + struct list_head gc_zero_ref_count_list; + struct list_head tmp_obj_list; /* used during GC */ + JSGCPhaseEnum gc_phase : 8; + size_t malloc_gc_threshold; +#ifdef DUMP_LEAKS + struct list_head string_list; /* list of JSString.link */ +#endif + /* stack limitation */ + const uint8_t* stack_top; + size_t stack_size; /* in bytes */ + + JSValue current_exception; + /* true if inside an out of memory error, to avoid recursing */ + BOOL in_out_of_memory : 8; + + struct JSStackFrame* current_stack_frame; + + JSInterruptHandler* interrupt_handler; + void* interrupt_opaque; + + JSHostPromiseRejectionTracker* host_promise_rejection_tracker; + void* host_promise_rejection_tracker_opaque; + + struct list_head job_list; /* list of JSJobEntry.link */ + + JSModuleNormalizeFunc* module_normalize_func; + JSModuleLoaderFunc* module_loader_func; + void* module_loader_opaque; + + BOOL can_block : 8; /* TRUE if Atomics.wait can block */ + /* used to allocate, free and clone SharedArrayBuffers */ + JSSharedArrayBufferFunctions sab_funcs; + + /* Shape hash table */ + int shape_hash_bits; + int shape_hash_size; + int shape_hash_count; /* number of hashed shapes */ + JSShape** shape_hash; +#ifdef CONFIG_BIGNUM + bf_context_t bf_ctx; + JSNumericOperations bigint_ops; + JSNumericOperations bigfloat_ops; + JSNumericOperations bigdecimal_ops; + uint32_t operator_count; +#endif + void* user_opaque; +}; + +typedef struct JSRegExp { + JSString* pattern; + JSString* bytecode; /* also contains the flags */ +} JSRegExp; + +typedef struct JSString JSString; + +struct JSObject { + union { + JSGCObjectHeader header; + struct { + int __gc_ref_count; /* corresponds to header.ref_count */ + 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 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() */ + uint8_t is_HTMLDDA : 1; /* specific annex B IsHtmlDDA behavior */ + uint16_t class_id; /* see JS_CLASS_x */ + }; + }; + /* byte offsets: 16/24 */ + JSShape* shape; /* prototype and property names + flag */ + void* prop; /* array of properties */ + /* byte offsets: 24/40 */ + struct JSMapRecord* first_weak_ref; /* XXX: use a bit and an external hash table? */ + /* byte offsets: 28/48 */ + union { + void* opaque; + struct JSBoundFunction* bound_function; /* JS_CLASS_BOUND_FUNCTION */ + struct JSCFunctionDataRecord* c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */ + struct JSForInIterator* for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */ + struct JSArrayBuffer* array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */ + struct JSTypedArray* typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */ +#ifdef CONFIG_BIGNUM + 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 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 */ + /* also used by JS_CLASS_GENERATOR_FUNCTION, JS_CLASS_ASYNC_FUNCTION and JS_CLASS_ASYNC_GENERATOR_FUNCTION */ + struct JSFunctionBytecode* function_bytecode; + void** var_refs; + JSObject* home_object; /* for 'super' access */ + } func; + struct { /* JS_CLASS_C_FUNCTION: 12/20 bytes */ + JSContext* realm; + JSCFunctionType c_function; + uint8_t length; + uint8_t cproto; + int16_t magic; + } cfunc; + /* array part for fast arrays and typed arrays */ + struct { /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS, JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + union { + uint32_t size; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ + struct JSTypedArray* typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + } u1; + union { + JSValue* values; /* JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS */ + void* ptr; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_FLOAT64_ARRAY */ + int8_t* int8_ptr; /* JS_CLASS_INT8_ARRAY */ + uint8_t* uint8_ptr; /* JS_CLASS_UINT8_ARRAY, JS_CLASS_UINT8C_ARRAY */ + int16_t* int16_ptr; /* JS_CLASS_INT16_ARRAY */ + uint16_t* uint16_ptr; /* JS_CLASS_UINT16_ARRAY */ + int32_t* int32_ptr; /* JS_CLASS_INT32_ARRAY */ + uint32_t* uint32_ptr; /* JS_CLASS_UINT32_ARRAY */ + int64_t* int64_ptr; /* JS_CLASS_INT64_ARRAY */ + uint64_t* uint64_ptr; /* JS_CLASS_UINT64_ARRAY */ + float* float_ptr; /* JS_CLASS_FLOAT32_ARRAY */ + double* double_ptr; /* JS_CLASS_FLOAT64_ARRAY */ + } u; + uint32_t count; /* <= 2^31-1. 0 for a detached typed array */ + } array; /* 12/20 bytes */ + JSRegExp regexp; /* JS_CLASS_REGEXP: 8/16 bytes */ + JSValue object_data; /* for JS_SetObjectData(): 8/16/16 bytes */ + } u; + /* byte sizes: 40/48/72 */ +}; + +uint16_t* JS_ToUnicode(JSContext* ctx, JSValueConst value, uint32_t* length) { + if (JS_VALUE_GET_TAG(value) != JS_TAG_STRING) { + value = JS_ToString(ctx, value); + if (JS_IsException(value)) + return nullptr; + } else { + value = JS_DupValue(ctx, value); + } + + uint16_t* buffer; + JSString* string = JS_VALUE_GET_STRING(value); + + if (!string->is_wide_char) { + uint8_t* p = string->u.str8; + uint32_t len = *length = string->len; + buffer = (uint16_t*)malloc(sizeof(uint16_t) * len * 2); + for (size_t i = 0; i < len; i++) { + buffer[i] = p[i]; + buffer[i + 1] = 0x00; + } + } else { + *length = string->len; + buffer = (uint16_t*)malloc(sizeof(uint16_t) * string->len); + memcpy(buffer, string->u.str16, sizeof(uint16_t) * string->len); + } + + JS_FreeValue(ctx, value); + return buffer; +} + +static JSString* js_alloc_string_rt(JSRuntime* rt, int max_len, int is_wide_char) { + JSString* str; + str = static_cast(js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char)); + if (unlikely(!str)) + return NULL; + str->header.ref_count = 1; + str->is_wide_char = is_wide_char; + str->len = max_len; + str->atom_type = 0; + str->hash = 0; /* optional but costless */ + str->hash_next = 0; /* optional */ +#ifdef DUMP_LEAKS + list_add_tail(&str->link, &rt->string_list); +#endif + return str; +} + +static JSString* js_alloc_string(JSRuntime* runtime, JSContext* ctx, int max_len, int is_wide_char) { + JSString* p; + p = js_alloc_string_rt(runtime, max_len, is_wide_char); + if (unlikely(!p)) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + return p; +} + +JSValue JS_NewUnicodeString(JSContext* ctx, const uint16_t* code, uint32_t length) { + JSString* str; + str = js_alloc_string(JS_GetRuntime(ctx), ctx, length, 1); + if (!str) + return JS_EXCEPTION; + memcpy(str->u.str16, code, length * 2); + return JS_MKPTR(JS_TAG_STRING, str); +} + +JSAtom JS_NewUnicodeAtom(JSContext* ctx, const uint16_t* code, uint32_t length) { + JSValue value = JS_NewUnicodeString(ctx, code, length); + JSAtom atom = JS_ValueToAtom(ctx, value); + JS_FreeValue(ctx, value); + return atom; +} + +JSClassID JSValueGetClassId(JSValue obj) { + JSObject* p; + if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) + return -1; + p = JS_VALUE_GET_OBJ(obj); + return p->class_id; +} + +bool JS_IsProxy(JSValue value) { + if (!JS_IsObject(value)) + return false; + JSObject* p = JS_VALUE_GET_OBJ(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; + return runtime->class_array[classId].class_id == classId; +} + +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; +} \ No newline at end of file diff --git a/bridge/bindings/qjs/qjs_engine_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h new file mode 100644 index 0000000000..bfaa420ae0 --- /dev/null +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_QJS_PATCH_H +#define BRIDGE_QJS_PATCH_H + +#include +#include + +struct JSString { + JSRefCountHeader header; /* must come first, 32-bit */ + uint32_t len : 31; + uint8_t is_wide_char : 1; /* 0 = 8 bits, 1 = 16 bits characters */ + /* for JS_ATOM_TYPE_SYMBOL: hash = 0, atom_type = 3, + for JS_ATOM_TYPE_PRIVATE: hash = 1, atom_type = 3 + XXX: could change encoding to have one more bit in hash */ + uint32_t hash : 30; + uint8_t atom_type : 2; /* != 0 if atom, JS_ATOM_TYPE_x */ + uint32_t hash_next; /* atom_index for JS_ATOM_TYPE_SYMBOL */ +#ifdef DUMP_LEAKS + struct list_head link; /* string list */ +#endif + union { + uint8_t str8[0]; /* 8 bit strings will get an extra null terminator */ + uint16_t str16[0]; + } 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 */ + JS_CLASS_ARRAY, /* u.array | length */ + JS_CLASS_ERROR, + JS_CLASS_NUMBER, /* u.object_data */ + JS_CLASS_STRING, /* u.object_data */ + JS_CLASS_BOOLEAN, /* u.object_data */ + JS_CLASS_SYMBOL, /* u.object_data */ + JS_CLASS_ARGUMENTS, /* u.array | length */ + JS_CLASS_MAPPED_ARGUMENTS, /* | length */ + JS_CLASS_DATE, /* u.object_data */ + JS_CLASS_MODULE_NS, + JS_CLASS_C_FUNCTION, /* u.cfunc */ + JS_CLASS_BYTECODE_FUNCTION, /* u.func */ + JS_CLASS_BOUND_FUNCTION, /* u.bound_function */ + JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */ + JS_CLASS_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */ + JS_CLASS_REGEXP, /* u.regexp */ + JS_CLASS_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_SHARED_ARRAY_BUFFER, /* u.array_buffer */ + JS_CLASS_UINT8C_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT8_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT16_ARRAY, /* u.array (typed_array) */ + JS_CLASS_INT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_UINT32_ARRAY, /* u.array (typed_array) */ +#ifdef CONFIG_BIGNUM + JS_CLASS_BIG_INT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_BIG_UINT64_ARRAY, /* u.array (typed_array) */ +#endif + JS_CLASS_FLOAT32_ARRAY, /* u.array (typed_array) */ + JS_CLASS_FLOAT64_ARRAY, /* u.array (typed_array) */ + JS_CLASS_DATAVIEW, /* u.typed_array */ +#ifdef CONFIG_BIGNUM + JS_CLASS_BIG_INT, /* u.object_data */ + JS_CLASS_BIG_FLOAT, /* u.object_data */ + JS_CLASS_FLOAT_ENV, /* u.float_env */ + JS_CLASS_BIG_DECIMAL, /* u.object_data */ + JS_CLASS_OPERATOR_SET, /* u.operator_set */ +#endif + JS_CLASS_MAP, /* u.map_state */ + JS_CLASS_SET, /* u.map_state */ + JS_CLASS_WEAKMAP, /* u.map_state */ + JS_CLASS_WEAKSET, /* u.map_state */ + JS_CLASS_MAP_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_SET_ITERATOR, /* u.map_iterator_data */ + JS_CLASS_ARRAY_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_STRING_ITERATOR, /* u.array_iterator_data */ + JS_CLASS_REGEXP_STRING_ITERATOR, /* u.regexp_string_iterator_data */ + JS_CLASS_GENERATOR, /* u.generator_data */ + JS_CLASS_PROXY, /* u.proxy_data */ + JS_CLASS_PROMISE, /* u.promise_data */ + JS_CLASS_PROMISE_RESOLVE_FUNCTION, /* u.promise_function_data */ + JS_CLASS_PROMISE_REJECT_FUNCTION, /* u.promise_function_data */ + JS_CLASS_ASYNC_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_FUNCTION_RESOLVE, /* u.async_function_data */ + JS_CLASS_ASYNC_FUNCTION_REJECT, /* u.async_function_data */ + JS_CLASS_ASYNC_FROM_SYNC_ITERATOR, /* u.async_from_sync_iterator_data */ + JS_CLASS_ASYNC_GENERATOR_FUNCTION, /* u.func */ + JS_CLASS_ASYNC_GENERATOR, /* u.async_generator_data */ + + JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ +}; + +#ifdef __cplusplus +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(JSContext* ctx, const uint16_t* code, uint32_t length); +JSAtom JS_NewUnicodeAtom(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 // BRIDGE_QJS_PATCH_H \ No newline at end of file diff --git a/bridge/bindings/qjs/qjs_patch_test.cc b/bridge/bindings/qjs/qjs_engine_patch_test.cc similarity index 90% rename from bridge/bindings/qjs/qjs_patch_test.cc rename to bridge/bindings/qjs/qjs_engine_patch_test.cc index 434a6f7d38..7ed9911cda 100644 --- a/bridge/bindings/qjs/qjs_patch_test.cc +++ b/bridge/bindings/qjs/qjs_engine_patch_test.cc @@ -3,7 +3,7 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#include "qjs_patch.h" +#include "qjs_engine_patch.h" #include #include "gtest/gtest.h" @@ -60,7 +60,7 @@ 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(ctx, reinterpret_cast(source.c_str()), source.length()); const char* str = JS_ToCString(ctx, result); EXPECT_STREQ(str, "helloworld"); @@ -74,7 +74,7 @@ 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(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/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc new file mode 100644 index 0000000000..2c0a1ddba1 --- /dev/null +++ b/bridge/bindings/qjs/qjs_function.cc @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "qjs_function.h" +#include +#include +#include "core/binding_object.h" +#include "core/dom/events/event_target.h" +#include "cppgc/gc_visitor.h" + +namespace webf { + +struct QJSFunctionCallbackContext { + QJSFunctionCallback qjs_function_callback; + void* private_data; +}; + +static JSValue HandleQJSFunctionCallback(JSContext* ctx, + JSValueConst this_val, + int argc, + JSValueConst* argv, + int magic, + JSValue* func_data) { + JSValue opaque_object = func_data[0]; + auto* callback_context = static_cast(JS_GetOpaque(opaque_object, JS_CLASS_OBJECT)); + std::vector arguments; + arguments.reserve(argc); + for (int i = 0; i < argc; i++) { + arguments.emplace_back(ScriptValue(ctx, argv[i])); + } + ScriptValue result = callback_context->qjs_function_callback(ctx, ScriptValue(ctx, this_val), argc, arguments.data(), + callback_context->private_data); + return JS_DupValue(ctx, result.QJSValue()); +} + +QJSFunction::QJSFunction(JSContext* ctx, QJSFunctionCallback qjs_function_callback, int32_t length, void* private_data) + : ctx_(ctx), runtime_(JS_GetRuntime(ctx)) { + JSValue opaque_object = JS_NewObject(ctx); + auto* context = new QJSFunctionCallbackContext{qjs_function_callback, private_data}; + JS_SetOpaque(opaque_object, context); + function_ = JS_NewCFunctionData(ctx, HandleQJSFunctionCallback, length, 0, 1, &opaque_object); + JS_FreeValue(ctx, opaque_object); +} + +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); + + ExecutingContext* context = ExecutingContext::From(ctx); + context->DrainPendingPromiseJobs(); + + // Free the previous duplicated function. + JS_FreeValue(ctx, function_); + + ScriptValue result = ScriptValue(ctx, returnValue); + JS_FreeValue(ctx, returnValue); + return result; +} + +void QJSFunction::Trace(GCVisitor* visitor) const { + visitor->Trace(function_); +} + +} // namespace webf diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h new file mode 100644 index 0000000000..ab5bfaadf7 --- /dev/null +++ b/bridge/bindings/qjs/qjs_function.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_QJS_FUNCTION_H +#define BRIDGE_QJS_FUNCTION_H + +#include "script_value.h" + +namespace webf { + +using QJSFunctionCallback = ScriptValue (*)(JSContext* ctx, + const ScriptValue& this_val, + uint32_t argc, + const ScriptValue* argv, + void* private_data); + +// 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); + } + static std::shared_ptr Create(JSContext* ctx, + QJSFunctionCallback qjs_function_callback, + int32_t length, + void* private_data) { + return std::make_shared(ctx, qjs_function_callback, length, private_data); + } + explicit QJSFunction(JSContext* ctx, JSValue function) + : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), function_(JS_DupValue(ctx, function)){}; + explicit QJSFunction(JSContext* ctx, QJSFunctionCallback qjs_function_callback, int32_t length, void* private_data); + ~QJSFunction() { JS_FreeValueRT(runtime_, 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}; + JSRuntime* runtime_{nullptr}; + JSValue function_{JS_NULL}; +}; + +} // namespace webf + +#endif // BRIDGE_QJS_FUNCTION_H diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h new file mode 100644 index 0000000000..db6fbac0e1 --- /dev/null +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ +#define BRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ + +#include "core/executing_context.h" +#include "script_wrappable.h" + +namespace webf { + +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 webf + +#endif // BRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ diff --git a/bridge/bindings/qjs/qjs_patch.c b/bridge/bindings/qjs/qjs_patch.c deleted file mode 100644 index 8d1a1ba927..0000000000 --- a/bridge/bindings/qjs/qjs_patch.c +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "qjs_patch.h" -#include -#include - -uint16_t* JS_ToUnicode(JSContext* ctx, JSValueConst value, uint32_t* length) { - if (JS_VALUE_GET_TAG(value) != JS_TAG_STRING) { - value = JS_ToString(ctx, value); - if (JS_IsException(value)) - return NULL; - } else { - value = JS_DupValue(ctx, value); - } - - uint16_t* buffer; - JSString* string = JS_VALUE_GET_STRING(value); - - if (!string->is_wide_char) { - uint8_t* p = string->u.str8; - uint32_t len = *length = string->len; - buffer = (uint16_t*)malloc(sizeof(uint16_t) * len * 2); - for (size_t i = 0; i < len; i++) { - buffer[i] = p[i]; - buffer[i + 1] = 0x00; - } - } else { - *length = string->len; - buffer = (uint16_t*)malloc(sizeof(uint16_t) * string->len); - memcpy(buffer, string->u.str16, sizeof(uint16_t) * string->len); - } - - JS_FreeValue(ctx, value); - return buffer; -} - -static JSString* js_alloc_string_rt(JSRuntime* rt, int max_len, int is_wide_char) { - JSString* str; - str = (JSString*)(js_malloc_rt(rt, sizeof(JSString) + (max_len << is_wide_char) + 1 - is_wide_char)); - if (unlikely(!str)) - return NULL; - str->header.ref_count = 1; - str->is_wide_char = is_wide_char; - str->len = max_len; - str->atom_type = 0; - str->hash = 0; /* optional but costless */ - str->hash_next = 0; /* optional */ -#ifdef DUMP_LEAKS - list_add_tail(&str->link, &rt->string_list); -#endif - return str; -} - -static JSString* js_alloc_string(JSRuntime* runtime, JSContext* ctx, int max_len, int is_wide_char) { - JSString* p; - p = js_alloc_string_rt(runtime, max_len, is_wide_char); - if (unlikely(!p)) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - return p; -} - -JSValue JS_NewUnicodeString(JSRuntime* runtime, JSContext* ctx, const uint16_t* code, uint32_t length) { - JSString* str; - str = js_alloc_string(runtime, ctx, length, 1); - if (!str) - return JS_EXCEPTION; - memcpy(str->u.str16, code, length * 2); - return JS_MKPTR(JS_TAG_STRING, str); -} - -JSClassID JSValueGetClassId(JSValue obj) { - JSObject* p; - if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT) - return -1; - p = JS_VALUE_GET_OBJ(obj); - return p->class_id; -} - -BOOL JS_IsProxy(JSValue value) { - if (!JS_IsObject(value)) - return FALSE; - JSObject* p = JS_VALUE_GET_OBJ(value); - return p->class_id == JS_CLASS_PROXY; -} - -BOOL JS_HasClassId(JSRuntime* runtime, JSClassID classId) { - if (runtime->class_count <= classId) - return FALSE; - return runtime->class_array[classId].class_id == classId; -} - -JSValue JS_GetProxyTarget(JSValue value) { - JSObject* p = JS_VALUE_GET_OBJ(value); - return p->u.proxy_data->target; -} diff --git a/bridge/bindings/qjs/qjs_patch.h b/bridge/bindings/qjs/qjs_patch.h deleted file mode 100644 index 5a578aa34f..0000000000 --- a/bridge/bindings/qjs/qjs_patch.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#ifndef BRIDGE_QJS_PATCH_H -#define BRIDGE_QJS_PATCH_H - -#include -#include -#include "quickjs/cutils.h" - -#ifdef __cplusplus -extern "C" { -#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); -JSClassID JSValueGetClassId(JSValue); -BOOL JS_IsProxy(JSValue value); -BOOL JS_HasClassId(JSRuntime* runtime, JSClassID classId); -JSValue JS_GetProxyTarget(JSValue value); - -#ifdef __cplusplus -} -#endif - -#endif // BRIDGE_QJS_PATCH_H diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index 1973d71a18..5e3b3b5bfe 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -4,64 +4,69 @@ */ #include "rejected_promises.h" -#include "executing_context.h" +#include "bindings/qjs/cppgc/mutation_scope.h" +#include "core/executing_context.h" -namespace webf::binding::qjs { +namespace webf { -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(); + + MemberMutationScope mutation_scope{context}; // 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 webf::binding::qjs +} // namespace webf diff --git a/bridge/bindings/qjs/rejected_promises.h b/bridge/bindings/qjs/rejected_promises.h index 9ba429aaee..eaeb74510e 100644 --- a/bridge/bindings/qjs/rejected_promises.h +++ b/bridge/bindings/qjs/rejected_promises.h @@ -11,15 +11,15 @@ #include #include -namespace webf::binding::qjs { +namespace webf { -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}; @@ -28,17 +28,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 webf::binding::qjs +} // namespace webf #endif // BRIDGE_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..e1f0d3cfb4 --- /dev/null +++ b/bridge/bindings/qjs/script_promise.cc @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "script_promise.h" +#include "qjs_engine_patch.h" + +namespace webf { + +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); +} + +ScriptPromise::ScriptPromise(JSContext* ctx, + std::shared_ptr* resolve_func, + std::shared_ptr* reject_func) { + JSValue resolving_funcs[2]; + JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); + + *resolve_func = QJSFunction::Create(ctx, resolving_funcs[0]); + *reject_func = QJSFunction::Create(ctx, resolving_funcs[1]); + + promise_ = ScriptValue(ctx, promise); +} + +JSValue ScriptPromise::ToQuickJS() { + return JS_DupValue(ctx_, promise_.QJSValue()); +} + +ScriptValue ScriptPromise::ToValue() const { + return promise_; +} + +void ScriptPromise::Trace(GCVisitor* visitor) {} + +} // namespace webf diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h new file mode 100644 index 0000000000..9802c24ee5 --- /dev/null +++ b/bridge/bindings/qjs/script_promise.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ +#define BRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ + +#include +#include "foundation/macros.h" +#include "qjs_function.h" +#include "script_value.h" + +namespace webf { + +// 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 { + WEBF_DISALLOW_NEW(); + + public: + ScriptPromise() = default; + ScriptPromise(JSContext* ctx, JSValue promise); + ScriptPromise(JSContext* ctx, std::shared_ptr* resolve_func, std::shared_ptr* reject_func); + + JSValue ToQuickJS(); + ScriptValue ToValue() const; + + void Trace(GCVisitor* visitor); + + private: + JSContext* ctx_; + ScriptValue promise_; +}; + +} // namespace webf + +#endif // BRIDGE_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..a8e70938fc --- /dev/null +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "script_promise_resolver.h" +#include "core/executing_context.h" + +namespace webf { + +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]; +} + +ScriptPromiseResolver::~ScriptPromiseResolver() { + JS_FreeValue(context_->ctx(), promise_); + JS_FreeValue(context_->ctx(), resolve_func_); + JS_FreeValue(context_->ctx(), reject_func_); +} + +void ScriptPromiseResolver::Trace(GCVisitor* visitor) const { + visitor->Trace(promise_); + visitor->Trace(resolve_func_); + visitor->Trace(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 webf diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h new file mode 100644 index 0000000000..95b01c2f41 --- /dev/null +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ +#define BRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ + +#include "converter_impl.h" +#include "to_quickjs.h" + +namespace webf { + +class GCVisitor; + +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); + } + + void Resolve(JSValue 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); + } + + void Reject(JSValue value) { ResolveOrReject(value, kRejecting); } + + void Trace(GCVisitor* visitor) const; + + private: + enum ResolutionState { + kPending, + kResolving, + kRejecting, + kDetached, + }; + + ExecutingContext* GetExecutionContext() const { return context_; } + + template + void ResolveOrReject(T value, ResolutionState new_state) { + JSValue qjs_value = toQuickJS(context_->ctx(), value); + ResolveOrReject(qjs_value, new_state); + JS_FreeValue(context_->ctx(), qjs_value); + } + + void ResolveOrReject(JSValue value, ResolutionState new_state) { + if (state_ != kPending || !context_->IsContextValid() || !context_) + return; + assert(new_state == kResolving || new_state == kRejecting); + state_ = new_state; + ResolveOrRejectImmediately(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 webf + +#endif // BRIDGE_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..38873173aa --- /dev/null +++ b/bridge/bindings/qjs/script_value.cc @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "script_value.h" +#include +#include "bindings/qjs/converter_impl.h" +#include "core/binding_object.h" +#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" +#include "qjs_event_target.h" + +namespace webf { + +static JSValue FromNativeValue(ExecutingContext* context, const NativeValue& native_value) { + switch (native_value.tag) { + case NativeTag::TAG_STRING: { + auto* string = static_cast(native_value.u.ptr); + if (string == nullptr) + return JS_NULL; + JSValue returnedValue = JS_NewUnicodeString(context->ctx(), string->string(), string->length()); + delete string; + return returnedValue; + } + case NativeTag::TAG_INT: { + return JS_NewUint32(context->ctx(), native_value.u.int64); + } + case NativeTag::TAG_BOOL: { + return JS_NewBool(context->ctx(), native_value.u.int64 == 1); + } + case NativeTag::TAG_FLOAT64: { + return JS_NewFloat64(context->ctx(), native_value.u.float64); + } + case NativeTag::TAG_NULL: { + return JS_NULL; + } + case NativeTag::TAG_LIST: { + size_t length = native_value.uint32; + auto* arr = static_cast(native_value.u.ptr); + JSValue array = JS_NewArray(context->ctx()); + JS_SetPropertyStr(context->ctx(), array, "length", Converter::ToValue(context->ctx(), length)); + for (int i = 0; i < length; i++) { + JSValue value = FromNativeValue(context, arr[i]); + JS_SetPropertyInt64(context->ctx(), array, i, value); + } + return array; + } + case NativeTag::TAG_JSON: { + auto* str = static_cast(native_value.u.ptr); + JSValue returnedValue = JS_ParseJSON(context->ctx(), str, strlen(str), ""); + delete str; + return returnedValue; + } + case NativeTag::TAG_POINTER: { + auto* ptr = static_cast(native_value.u.ptr); + auto* binding_object = BindingObject::From(ptr); + + // Only eventTarget can be converted from nativeValue to JSValue. + auto* event_target = DynamicTo(binding_object); + if (event_target) { + return event_target->ToQuickJS(); + } + + return JS_NULL; + } + case NativeTag::TAG_FUNCTION: { + return NativeValueConverter::FromNativeValue(context->ctx(), native_value)->ToQuickJS(); + } + case NativeTag::TAG_ASYNC_FUNCTION: { + return NativeValueConverter::FromNativeValue(context->ctx(), native_value)->ToQuickJS(); + } + } + return JS_NULL; +} + +ScriptValue::ScriptValue(JSContext* ctx, const NativeValue& native_value) + : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), value_(FromNativeValue(ExecutingContext::From(ctx), native_value)) {} + +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 {ctx_, value_}; +} + +NativeValue ScriptValue::ToNative(ExceptionState& exception_state) const { + int8_t tag = JS_VALUE_GET_TAG(value_); + + switch (tag) { + case JS_TAG_NULL: + case JS_TAG_UNDEFINED: + return Native_NewNull(); + case JS_TAG_BOOL: + return Native_NewBool(JS_ToBool(ctx_, value_)); + case JS_TAG_FLOAT64: { + double v; + JS_ToFloat64(ctx_, &v, value_); + return Native_NewFloat64(v); + } + case JS_TAG_INT: { + int32_t v; + JS_ToInt32(ctx_, &v, value_); + return Native_NewInt64(v); + } + case JS_TAG_STRING: + // NativeString owned by NativeValue will be freed by users. + return NativeValueConverter::ToNativeValue(ToString()); + case JS_TAG_OBJECT: { + if (JS_IsArray(ctx_, value_)) { + std::vector values = + Converter>::FromValue(ctx_, value_, ASSERT_NO_EXCEPTION()); + auto* result = new NativeValue[values.size()]; + for (int i = 0; i < values.size(); i++) { + result[i] = values[i].ToNative(exception_state); + } + return Native_NewList(values.size(), result); + } else if (JS_IsObject(value_)) { + if (QJSEventTarget::HasInstance(ExecutingContext::From(ctx_), value_)) { + auto* event_target = toScriptWrappable(value_); + return Native_NewPtr(JSPointerType::Others, event_target->bindingObject()); + } + return NativeValueConverter::ToNativeValue(*this, exception_state); + } + } + default: + return Native_NewNull(); + } +} + +bool ScriptValue::IsException() const { + return JS_IsException(value_); +} + +bool ScriptValue::IsEmpty() const { + return JS_IsNull(value_) || JS_IsUndefined(value_); +} + +bool ScriptValue::IsObject() const { + return JS_IsObject(value_); +} + +bool ScriptValue::IsString() const { + return JS_IsString(value_); +} + +bool ScriptValue::IsNull() const { + return JS_IsNull(value_); +} + +bool ScriptValue::IsUndefined() const { + return JS_IsUndefined(value_); +} + +bool ScriptValue::IsBool() const { + return JS_IsBool(value_); +} + +void ScriptValue::Trace(GCVisitor* visitor) const { + visitor->Trace(value_); +} + +} // namespace webf diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h new file mode 100644 index 0000000000..5ebd8397a9 --- /dev/null +++ b/bridge/bindings/qjs/script_value.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_SCRIPT_VALUE_H +#define BRIDGE_SCRIPT_VALUE_H + +#include +#include + +#include "atomic_string.h" +#include "exception_state.h" +#include "foundation/macros.h" +#include "foundation/native_string.h" +#include "qjs_engine_patch.h" + +namespace webf { + +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. + WEBF_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)), runtime_(JS_GetRuntime(ctx)){}; + explicit ScriptValue(JSContext* ctx, const NativeString* string) + : ctx_(ctx), value_(JS_NewUnicodeString(ctx, string->string(), string->length())), runtime_(JS_GetRuntime(ctx)) {} + explicit ScriptValue(JSContext* ctx, double v) + : ctx_(ctx), value_(JS_NewFloat64(ctx, v)), runtime_(JS_GetRuntime(ctx)) {} + explicit ScriptValue(JSContext* ctx) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)){}; + explicit ScriptValue(JSContext* ctx, const NativeValue& native_value); + 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(ExceptionState& exception_state) const; + + bool IsException() const; + bool IsEmpty() const; + bool IsObject() const; + bool IsString() const; + bool IsNull() const; + bool IsUndefined() const; + bool IsBool() const; + + void Trace(GCVisitor* visitor) const; + + private: + JSContext* ctx_{nullptr}; + JSRuntime* runtime_{nullptr}; + JSValue value_{JS_NULL}; +}; + +template +class ScriptValueConverter { + using ImplType = T; +}; + +} // namespace webf + +#endif // BRIDGE_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..f80136ae12 --- /dev/null +++ b/bridge/bindings/qjs/script_value_test.cc @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "script_value.h" +#include +#include +#include "atomic_string.h" +#include "gtest/gtest.h" + +using namespace webf; + +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..d624314938 --- /dev/null +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "script_wrappable.h" +#include "core/executing_context.h" +#include "cppgc/gc_visitor.h" + +namespace webf { + +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(); + + ExecutingContext* context = ExecutingContext::From(ctx); + JSValue prototypeObject = context->contextData()->prototypeForType(wrapper_type_info); + if (JS_HasProperty(ctx, prototypeObject, atom)) { + JSValue target = JS_DupValue(ctx, prototypeObject); + JSValue setterFunc = JS_UNDEFINED; + while (JS_IsUndefined(setterFunc)) { + JSPropertyDescriptor descriptor; + descriptor.setter = JS_UNDEFINED; + descriptor.getter = JS_UNDEFINED; + descriptor.value = JS_UNDEFINED; + JS_GetOwnProperty(ctx, &descriptor, target, atom); + setterFunc = descriptor.setter; + if (JS_IsFunction(ctx, setterFunc)) { + JS_FreeValue(ctx, descriptor.getter); + break; + } + + JSValue new_target = JS_GetPrototype(ctx, target); + JS_FreeValue(ctx, target); + target = new_target; + JS_FreeValue(ctx, descriptor.getter); + JS_FreeValue(ctx, descriptor.setter); + } + + assert_m(JS_IsFunction(ctx, setterFunc), "Setter on prototype should be an function."); + JSValue ret = JS_Call(ctx, setterFunc, obj, 1, &value); + if (JS_IsException(ret)) + return -1; + + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, setterFunc); + JS_FreeValue(ctx, target); + return 0; + } + + if (wrapper_type_info->indexed_property_setter_handler_ != nullptr && JS_AtomIsTaggedInt(atom)) { + return wrapper_type_info->indexed_property_setter_handler_(ctx, obj, JS_AtomToUInt32(atom), value); + } else if (wrapper_type_info->string_property_setter_handler_ != nullptr) { + 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->property_checker_handler_(ctx, obj, atom); +} + +/// This callback will be called when JS code enumerate all own properties on this object. +/// Exp: Object.keys(obj); +static int HandleJSPropertyEnumerateCallback(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, JSValueConst obj) { + auto* object = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* wrapper_type_info = object->GetWrapperTypeInfo(); + + return wrapper_type_info->property_enumerate_handler_(ctx, ptab, plen, obj); +} + +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->property_checker_handler_ != nullptr)) { + exotic_methods->has_property = HandleJSPropertyCheckerCallback; + } + + if (UNLIKELY(wrapper_type_info->property_enumerate_handler_ != nullptr)) { + exotic_methods->get_own_property_names = HandleJSPropertyEnumerateCallback; + exotic_methods->get_own_property = [](JSContext* ctx, JSPropertyDescriptor* desc, JSValueConst obj, + JSAtom prop) -> int { + auto* object = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* wrapper_type_info = object->GetWrapperTypeInfo(); + + if (wrapper_type_info->string_property_getter_handler_ != nullptr) { + JSValue return_value = wrapper_type_info->string_property_getter_handler_(ctx, obj, prop); + if (!JS_IsNull(return_value)) { + desc->flags = JS_PROP_ENUMERABLE; + desc->value = return_value; + desc->getter = JS_NULL; + desc->setter = JS_NULL; + return true; + } + } + + if (wrapper_type_info->indexed_property_getter_handler_ != nullptr) { + uint32_t index = JS_AtomToUInt32(prop); + JSValue return_value = wrapper_type_info->indexed_property_getter_handler_(ctx, obj, index); + if (!JS_IsNull(return_value)) { + desc->flags = JS_PROP_ENUMERABLE; + desc->value = return_value; + desc->getter = JS_NULL; + desc->setter = JS_NULL; + return true; + } + } + + return false; + }; + } else { + // 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); + + if (KeepAlive()) { + JS_DupValue(ctx_, jsObject_); + context_->RegisterActiveScriptWrappers(this); + } + + // Let our instance into inherit prototype methods. + JSValue prototype = GetExecutingContext()->contextData()->prototypeForType(wrapper_type_info); + JS_SetPrototype(ctx_, jsObject_, prototype); +} + +bool ScriptWrappable::KeepAlive() const { + return false; +} + +} // namespace webf diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h new file mode 100644 index 0000000000..f4462d8e2e --- /dev/null +++ b/bridge/bindings/qjs/script_wrappable.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_SCRIPT_WRAPPABLE_H +#define BRIDGE_SCRIPT_WRAPPABLE_H + +#include +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "core/executing_context.h" +#include "foundation/macros.h" +#include "wrapper_type_info.h" + +namespace webf { + +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{}; + + virtual 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; + + /** + * Classes kept alive as long as + * they have a pending activity. Destroying the corresponding ExecutionContext + * implicitly releases them to avoid leaks. + */ + virtual bool KeepAlive() const; + + 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 webf + +#endif // BRIDGE_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..26b66687a8 --- /dev/null +++ b/bridge/bindings/qjs/source_location.cc @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "source_location.h" + +namespace webf { + +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 webf diff --git a/bridge/bindings/qjs/source_location.h b/bridge/bindings/qjs/source_location.h new file mode 100644 index 0000000000..ce9de59442 --- /dev/null +++ b/bridge/bindings/qjs/source_location.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ +#define BRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ + +#include +#include + +namespace webf { + +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 webf + +#endif // BRIDGE_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..e6fe578921 --- /dev/null +++ b/bridge/bindings/qjs/to_quickjs.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ +#define BRIDGE_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 webf { + +// 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 webf + +#endif // BRIDGE_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..2b36128206 --- /dev/null +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_WRAPPER_TYPE_INFO_H +#define BRIDGE_WRAPPER_TYPE_INFO_H + +#include +#include +#include "bindings/qjs/qjs_engine_patch.h" + +namespace webf { + +class EventTarget; +class TouchList; + +// 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_UI_EVENT, + JS_CLASS_CLOSE_EVENT, + JS_CLASS_TOUCH_EVENT, + JS_CLASS_POINTER_EVENT, + JS_CLASS_MOUSE_EVENT, + JS_CLASS_CUSTOM_EVENT, + JS_CLASS_TRANSITION_EVENT, + JS_CLASS_INPUT_EVENT, + JS_CLASS_ANIMATION_EVENT, + JS_CLASS_FOCUS_EVENT, + JS_CLASS_GESTURE_EVENT, + JS_CLASS_POP_STATE_EVENT, + JS_CLASS_INTERSECTION_CHANGE_EVENT, + JS_CLASS_KEYBOARD_EVENT, + JS_CLASS_PROMISE_REJECTION_EVENT, + JS_CLASS_EVENT_TARGET, + JS_CLASS_TOUCH, + JS_CLASS_TOUCH_LIST, + JS_CLASS_WINDOW, + JS_CLASS_NODE, + JS_CLASS_ELEMENT, + JS_CLASS_SCREEN, + JS_CLASS_PERFORMANCE, + JS_CLASS_PERFORMANCE_MARK, + JS_CLASS_PERFORMANCE_ENTRY, + JS_CLASS_PERFORMANCE_MEASURE, + 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_ALL_COLLECTION, + JS_CLASS_HTML_ELEMENT, + JS_CLASS_WIDGET_ELEMENT, + JS_CLASS_HTML_DIV_ELEMENT, + JS_CLASS_HTML_BODY_ELEMENT, + JS_CLASS_HTML_HEAD_ELEMENT, + JS_CLASS_HTML_HTML_ELEMENT, + JS_CLASS_HTML_IMAGE_ELEMENT, + JS_CLASS_HTML_SCRIPT_ELEMENT, + JS_CLASS_HTML_ANCHOR_ELEMENT, + JS_CLASS_HTML_LINK_ELEMENT, + JS_CLASS_HTML_CANVAS_ELEMENT, + JS_CLASS_IMAGE, + JS_CLASS_CANVAS_RENDERING_CONTEXT, + JS_CLASS_CANVAS_RENDERING_CONTEXT_2_D, + JS_CLASS_HTML_TEMPLATE_ELEMENT, + JS_CLASS_HTML_UNKNOWN_ELEMENT, + JS_CLASS_HTML_INPUT_ELEMENT, + JS_CLASS_HTML_BUTTON_ELEMENT, + JS_CLASS_HTML_TEXTAREA_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 PropertyCheckerHandler = bool (*)(JSContext* ctx, JSValueConst obj, JSAtom atom); + +// Callback when enums all property on object. +// exp: Object.keys(obj); +using PropertyEnumerateHandler = int (*)(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, JSValueConst obj); + +// 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}; + PropertyCheckerHandler property_checker_handler_{nullptr}; + PropertyEnumerateHandler property_enumerate_handler_{nullptr}; +}; + +} // namespace webf + +#endif // BRIDGE_WRAPPER_TYPE_INFO_H diff --git a/bridge/core/binding_call_methods.json5 b/bridge/core/binding_call_methods.json5 new file mode 100644 index 0000000000..bdf23811ea --- /dev/null +++ b/bridge/core/binding_call_methods.json5 @@ -0,0 +1,157 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "binding_call_methods" + } + ] + }, + "data": [ + "click", + "scroll", + "scrollBy", + "clientTop", + "clientLeft", + "clientWidth", + "clientHeight", + "scrollLeft", + "scrollTop", + "offsetTop", + "offsetLeft", + "offsetWidth", + "offsetHeight", + "scrollWidth", + "scrollHeight", + "getBoundingClientRect", + ["getPropertyMagic", "%g"], + ["setPropertyMagic", "%s"], + "open", + "devicePixelRatio", + "colorScheme", + "scrollX", + "scrollY", + "innerWidth", + "innerHeight", + "availWidth", + "availHeight", + "width", + "height", + "top", + "bottom", + "left", + "right", + "x", + "y", + "z", + "screen", + "target", + "accessKey", + "download", + "ping", + "rel", + "type", + "text", + "href", + "origin", + "protocol", + "username", + "password", + "host", + "hostname", + "port", + "pathname", + "search", + "hash", + "alt", + "src", + "srcset", + "sizes", + "naturalWidth", + "naturalHeight", + "complete", + "currentSrc", + "decoding", + "fetchPriority", + "loading", + "noModule", + "async", + "getContext", + "fillStyle", + "direction", + "font", + "strokeStyle", + "lineCap", + "lineDashOffset", + "lineJoin", + "lineWidth", + "miterLimit", + "textAlign", + "textBaseline", + "arc", + "arcTo", + "beginPath", + "bezierCurveTo", + "clearRect", + "closePath", + "clip", + "drawImage", + "ellipse", + "fill", + "fillRect", + "fillText", + "lineTo", + "moveTo", + "rect", + "restore", + "resetTransform", + "rotate", + "quadraticCurveTo", + "stroke", + "strokeRect", + "save", + "scale", + "strokeText", + "setTransform", + "transform", + "translate", + "reset", + "focus", + "blur", + "defaultValue", + "value", + "accept", + "autocomplete", + "autofocus", + "checked", + "disabled", + "min", + "max", + "minLength", + "maxLength", + "size", + "multiple", + "name", + "step", + "pattern", + "required", + "readonly", + "placeholder", + "inputMode", + "cols", + "rows", + "wrap", + "dispatchEvent", + "getModifierState", + "querySelector", + "querySelectorAll", + "getElementById", + "getElementsByClassName", + "getElementsByName", + "getElementsByTagName", + "id", + "className", + "cookie", + "class" + ] +} diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc new file mode 100644 index 0000000000..b5d1da1ae1 --- /dev/null +++ b/bridge/core/binding_object.cc @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "binding_object.h" +#include "binding_call_methods.h" +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/script_promise_resolver.h" +#include "core/dom/events/event_target.h" +#include "core/executing_context.h" +#include "foundation/native_value_converter.h" + +namespace webf { + +void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_object, + NativeValue* return_value, + NativeValue* 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() { + // Set below properties to nullptr to avoid dart callback to native. + binding_object_->disposed_ = true; + binding_object_->binding_target_ = nullptr; + binding_object_->invoke_binding_methods_from_dart = nullptr; + binding_object_->invoke_bindings_methods_from_native = nullptr; +} + +BindingObject::BindingObject(ExecutingContext* context, NativeBindingObject* native_binding_object) + : context_(context) { + native_binding_object->binding_target_ = this; + native_binding_object->invoke_binding_methods_from_dart = NativeBindingObject::HandleCallFromDartSide; + binding_object_ = native_binding_object; +} + +void BindingObject::TrackPendingPromiseBindingContext(BindingObjectPromiseContext* binding_object_promise_context) { + pending_promise_contexts_.emplace(binding_object_promise_context); +} + +void BindingObject::FullFillPendingPromise(BindingObjectPromiseContext* binding_object_promise_context) { + pending_promise_contexts_.erase(binding_object_promise_context); +} + +NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, + int32_t argc, + const NativeValue* argv, + ExceptionState& exception_state) const { + context_->FlushUICommand(); + if (binding_object_->invoke_bindings_methods_from_native == nullptr) { + exception_state.ThrowException(context_->ctx(), ErrorType::InternalError, + "Failed to call dart method: invoke_bindings_methods_from_native not initialized."); + return Native_NewNull(); + } + + NativeValue return_value = Native_NewNull(); + NativeValue native_method = NativeValueConverter::ToNativeValue(method); + binding_object_->invoke_bindings_methods_from_native(binding_object_, &return_value, &native_method, argc, argv); + return return_value; +} + +NativeValue BindingObject::InvokeBindingMethod(BindingMethodCallOperations binding_method_call_operation, + int32_t argc, + const NativeValue* argv, + ExceptionState& exception_state) const { + context_->FlushUICommand(); + if (binding_object_->invoke_bindings_methods_from_native == nullptr) { + exception_state.ThrowException(context_->ctx(), ErrorType::InternalError, + "Failed to call dart method: invoke_bindings_methods_from_native not initialized."); + return Native_NewNull(); + } + + NativeValue return_value = Native_NewNull(); + NativeValue native_method = NativeValueConverter::ToNativeValue(binding_method_call_operation); + binding_object_->invoke_bindings_methods_from_native(binding_object_, &return_value, &native_method, 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(BindingMethodCallOperations::kGetProperty, 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(BindingMethodCallOperations::kSetProperty, 2, argv, exception_state); +} + +ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, + const ScriptValue& this_val, + uint32_t argc, + const ScriptValue* argv, + void* private_data) { + auto id = reinterpret_cast(private_data); + auto* event_target = toScriptWrappable(this_val.QJSValue()); + + std::vector arguments; + arguments.reserve(argc + 1); + arguments.emplace_back(NativeValueConverter::ToNativeValue(id)); + + ExceptionState exception_state; + + for (int i = 0; i < argc; i++) { + arguments.emplace_back(argv[i].ToNative(exception_state)); + } + + if (exception_state.HasException()) { + event_target->GetExecutingContext()->HandleException(exception_state); + return ScriptValue::Empty(ctx); + } + + NativeValue result = event_target->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, + arguments.size(), arguments.data(), exception_state); + + if (exception_state.HasException()) { + event_target->GetExecutingContext()->HandleException(exception_state); + return ScriptValue::Empty(ctx); + } + return ScriptValue(ctx, result); +} + +void BindingObject::HandleAnonymousAsyncCalledFromDart(void* ptr, + NativeValue* native_value, + int32_t contextId, + const char* errmsg) { + auto* promise_context = static_cast(ptr); + if (!promise_context->context->IsContextValid()) + return; + if (promise_context->context->contextId() != contextId) + return; + + auto* context = promise_context->context; + + if (native_value != nullptr) { + ScriptValue params = ScriptValue(context->ctx(), *native_value); + promise_context->promise_resolver->Resolve(params.QJSValue()); + } else if (errmsg != nullptr) { + ExceptionState exception_state; + exception_state.ThrowException(context->ctx(), ErrorType::TypeError, errmsg); + JSValue error_object = JS_GetException(context->ctx()); + promise_context->promise_resolver->Reject(error_object); + JS_FreeValue(context->ctx(), error_object); + } + + promise_context->binding_object->FullFillPendingPromise(promise_context); + + delete promise_context; +} + +ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, + const ScriptValue& this_val, + uint32_t argc, + const ScriptValue* argv, + void* private_data) { + auto id = reinterpret_cast(private_data); + auto* event_target = toScriptWrappable(this_val.QJSValue()); + + auto promise_resolver = ScriptPromiseResolver::Create(event_target->GetExecutingContext()); + + auto* promise_context = + new BindingObjectPromiseContext{{}, event_target->GetExecutingContext(), event_target, promise_resolver}; + event_target->TrackPendingPromiseBindingContext(promise_context); + + std::vector arguments; + arguments.reserve(argc + 4); + + arguments.emplace_back(NativeValueConverter::ToNativeValue(id)); + arguments.emplace_back( + NativeValueConverter::ToNativeValue(event_target->GetExecutingContext()->contextId())); + arguments.emplace_back( + NativeValueConverter>::ToNativeValue(promise_context)); + arguments.emplace_back(NativeValueConverter>::ToNativeValue( + reinterpret_cast(HandleAnonymousAsyncCalledFromDart))); + + ExceptionState exception_state; + + for (int i = 0; i < argc; i++) { + arguments.emplace_back(argv[i].ToNative(exception_state)); + } + + event_target->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, arguments.data(), + exception_state); + + if (exception_state.HasException()) { + event_target->GetExecutingContext()->HandleException(exception_state); + return ScriptValue::Empty(ctx); + } + + return promise_resolver->Promise().ToValue(); +} + +NativeValue BindingObject::GetAllBindingPropertyNames(ExceptionState& exception_state) const { + context_->FlushUICommand(); + return InvokeBindingMethod(BindingMethodCallOperations::kGetAllPropertyNames, 0, nullptr, exception_state); +} + +void BindingObject::Trace(GCVisitor* visitor) const { + for (auto&& promise_context : pending_promise_contexts_) { + promise_context->promise_resolver->Trace(visitor); + } +} + +bool BindingObject::IsEventTarget() const { + return false; +} + +bool BindingObject::IsTouchList() const { + return false; +} + +} // namespace webf diff --git a/bridge/core/binding_object.h b/bridge/core/binding_object.h new file mode 100644 index 0000000000..a32f2f7f02 --- /dev/null +++ b/bridge/core/binding_object.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_BINDING_OBJECT_H_ +#define BRIDGE_CORE_DOM_BINDING_OBJECT_H_ + +#include +#include +#include "bindings/qjs/atomic_string.h" +#include "foundation/native_type.h" +#include "foundation/native_value.h" + +namespace webf { + +class BindingObject; +class NativeBindingObject; +class ExceptionState; +class GCVisitor; +class ScriptPromiseResolver; + +using InvokeBindingsMethodsFromNative = void (*)(const NativeBindingObject* binding_object, + NativeValue* return_value, + NativeValue* method, + int32_t argc, + const NativeValue* argv); + +using InvokeBindingMethodsFromDart = void (*)(NativeBindingObject* binding_object, + NativeValue* return_value, + NativeValue* method, + int32_t argc, + NativeValue* argv); + +struct NativeBindingObject : public DartReadable { + NativeBindingObject() = delete; + explicit NativeBindingObject(BindingObject* target) + : binding_target_(target), invoke_binding_methods_from_dart(HandleCallFromDartSide){}; + + static void HandleCallFromDartSide(NativeBindingObject* binding_object, + NativeValue* return_value, + NativeValue* method, + int32_t argc, + NativeValue* argv); + + bool disposed_{false}; + BindingObject* binding_target_{nullptr}; + InvokeBindingMethodsFromDart invoke_binding_methods_from_dart{nullptr}; + InvokeBindingsMethodsFromNative invoke_bindings_methods_from_native{nullptr}; +}; + +enum BindingMethodCallOperations { + kGetProperty, + kSetProperty, + kGetAllPropertyNames, + kAnonymousFunctionCall, + kAsyncAnonymousFunction, +}; + +struct BindingObjectPromiseContext : public DartReadable { + ExecutingContext* context; + BindingObject* binding_object; + std::shared_ptr promise_resolver; +}; + +class BindingObject { + public: + // This function were called when the anonymous function returned to the JS code has been called by users. + static ScriptValue AnonymousFunctionCallback(JSContext* ctx, + const ScriptValue& this_val, + uint32_t argc, + const ScriptValue* argv, + void* private_data); + static ScriptValue AnonymousAsyncFunctionCallback(JSContext* ctx, + const ScriptValue& this_val, + uint32_t argc, + const ScriptValue* argv, + void* private_data); + static void HandleAnonymousAsyncCalledFromDart(void* ptr, + NativeValue* native_value, + int32_t contextId, + const char* errmsg); + + BindingObject() = delete; + ~BindingObject(); + explicit BindingObject(ExecutingContext* context); + + // Handle call from dart side. + virtual NativeValue HandleCallFromDartSide(const NativeValue* method, int32_t argc, const NativeValue* argv) = 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; + NativeValue GetAllBindingPropertyNames(ExceptionState& exception_state) const; + + NativeBindingObject* bindingObject() const { return binding_object_; } + + void Trace(GCVisitor* visitor) const; + + inline static BindingObject* From(NativeBindingObject* native_binding_object) { + if (native_binding_object == nullptr) + return nullptr; + + return native_binding_object->binding_target_; + }; + + virtual bool IsEventTarget() const; + virtual bool IsTouchList() const; + + protected: + void TrackPendingPromiseBindingContext(BindingObjectPromiseContext* binding_object_promise_context); + void FullFillPendingPromise(BindingObjectPromiseContext* binding_object_promise_context); + NativeValue InvokeBindingMethod(BindingMethodCallOperations binding_method_call_operation, + int32_t argc, + const NativeValue* args, + ExceptionState& exception_state) const; + + // NativeBindingObject may allocated at Dart side. Binding this with Dart allocated NativeBindingObject. + explicit BindingObject(ExecutingContext* context, NativeBindingObject* native_binding_object); + + private: + ExecutingContext* context_{nullptr}; + NativeBindingObject* binding_object_{new NativeBindingObject(this)}; + std::set pending_promise_contexts_; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_DOM_BINDING_OBJECT_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/bindings/qjs/dom/css_property_list.h b/bridge/core/css/legacy/css_property_list.h similarity index 99% rename from bridge/bindings/qjs/dom/css_property_list.h rename to bridge/core/css/legacy/css_property_list.h index 646ebb3135..fae1eafa82 100644 --- a/bridge/bindings/qjs/dom/css_property_list.h +++ b/bridge/core/css/legacy/css_property_list.h @@ -3,10 +3,9 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#ifndef BRIDGE_BINDINGS_QJS_DOM_CSS_PROPERTY_LIST_H_ -#define BRIDGE_BINDINGS_QJS_DOM_CSS_PROPERTY_LIST_H_ +#ifndef BRIDGE_CORE_CSS_LEGACY_CSS_PROPERTY_LIST_H_ +#define BRIDGE_CORE_CSS_LEGACY_CSS_PROPERTY_LIST_H_ -#include #include namespace webf { @@ -433,4 +432,4 @@ std::unordered_map cssPropertyList{{"accentColor", true}, } // namespace webf -#endif // BRIDGE_BINDINGS_QJS_DOM_CSS_PROPERTY_LIST_H_ +#endif // BRIDGE_CORE_CSS_LEGACY_CSS_PROPERTY_LIST_H_ 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..50058ef1b0 --- /dev/null +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "css_style_declaration.h" +#include +#include "core/dom/element.h" +#include "core/executing_context.h" +#include "css_property_list.h" + +namespace webf { + +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; +} + +bool CSSStyleDeclaration::NamedPropertyQuery(const AtomicString& key, ExceptionState&) { + return cssPropertyList.count(key.ToStdString()) > 0; +} + +void CSSStyleDeclaration::NamedPropertyEnumerator(std::vector& names, ExceptionState&) { + for (auto& entry : cssPropertyList) { + names.emplace_back(AtomicString(ctx(), entry.first)); + } +} + +AtomicString CSSStyleDeclaration::InternalGetPropertyValue(std::string& name) { + name = parseJavaScriptCSSPropertyName(name); + + if (LIKELY(properties_.count(name) > 0)) { + return properties_[name]; + } + + return AtomicString::Empty(); +} + +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(); + } + + 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 webf 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..94c03e2136 --- /dev/null +++ b/bridge/core/css/legacy/css_style_declaration.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CSS_STYLE_DECLARATION_H +#define BRIDGE_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 webf { + +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; + + bool NamedPropertyQuery(const AtomicString&, ExceptionState&); + void NamedPropertyEnumerator(std::vector& names, ExceptionState&); + + 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 webf + +#endif // BRIDGE_CSS_STYLE_DECLARATION_H diff --git a/bridge/core/css/legacy/css_style_declaration_test.cc b/bridge/core/css/legacy/css_style_declaration_test.cc new file mode 100644 index 0000000000..3f1cd3d5cd --- /dev/null +++ b/bridge/core/css/legacy/css_style_declaration_test.cc @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "gtest/gtest.h" +#include "webf_test_env.h" + +using namespace webf; + +TEST(CSSStyleDeclaration, setStyleData) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "document.documentElement.style.backgroundColor = 'white';" + "document.documentElement.style.backgroundColor = 'white';"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); +} + +TEST(CSSStyleDeclaration, enumerateStyles) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = "console.assert(Object.keys(document.body.style).length > 400)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); +} diff --git a/bridge/core/dart_methods.cc b/bridge/core/dart_methods.cc new file mode 100644 index 0000000000..ce348d5dcb --- /dev/null +++ b/bridge/core/dart_methods.cc @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "dart_methods.h" +#include + +namespace webf { + +webf::DartMethodPointer::DartMethodPointer(const uint64_t* dart_methods, int32_t dart_methods_length) { + size_t i = 0; + invokeModule = reinterpret_cast(dart_methods[i++]); + requestBatchUpdate = reinterpret_cast(dart_methods[i++]); + reloadApp = reinterpret_cast(dart_methods[i++]); + setTimeout = reinterpret_cast(dart_methods[i++]); + setInterval = reinterpret_cast(dart_methods[i++]); + clearTimeout = reinterpret_cast(dart_methods[i++]); + requestAnimationFrame = reinterpret_cast(dart_methods[i++]); + cancelAnimationFrame = reinterpret_cast(dart_methods[i++]); + toBlob = reinterpret_cast(dart_methods[i++]); + flushUICommand = reinterpret_cast(dart_methods[i++]); + +#if ENABLE_PROFILE + dartMethodPointer->getPerformanceEntries = reinterpret_cast(dart_methods[i++]); +#else + i++; +#endif + + onJsError = reinterpret_cast(dart_methods[i++]); + onJsLog = reinterpret_cast(dart_methods[i++]); + + assert_m(i == dart_methods_length, "Dart native methods count is not equal with C++ side method registrations."); +} +} // namespace webf \ No newline at end of file diff --git a/bridge/include/dart_methods.h b/bridge/core/dart_methods.h similarity index 55% rename from bridge/include/dart_methods.h rename to bridge/core/dart_methods.h index 678b9e3f83..8cbe605f2c 100644 --- a/bridge/include/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -1,25 +1,39 @@ /* - * Copyright (C) 2019-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #ifndef WEBF_DART_METHODS_H_ #define WEBF_DART_METHODS_H_ -#include "webf_bridge.h" +/// Functions implements at dart side, including timer, Rendering and module API. +/// Communicate via Dart FFI. #include #include +#include "webf_bridge.h" + +#include "foundation/native_string.h" +#include "foundation/native_value.h" #define WEBF_EXPORT __attribute__((__visibility__("default"))) -struct NativeString; -struct NativeScreen; +namespace webf { using AsyncCallback = void (*)(void* callbackContext, int32_t contextId, const char* errmsg); using AsyncRAFCallback = void (*)(void* callbackContext, int32_t contextId, double result, const char* errmsg); -using AsyncModuleCallback = void (*)(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json); -using 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 AsyncModuleCallback = NativeValue* (*)(void* callbackContext, + int32_t contextId, + const char* errmsg, + NativeValue* value); +using AsyncBlobCallback = + void (*)(void* callbackContext, int32_t contextId, const char* error, uint8_t* bytes, int32_t length); +typedef NativeValue* (*InvokeModule)(void* callbackContext, + int32_t contextId, + NativeString* moduleName, + NativeString* method, + NativeValue* params, + AsyncModuleCallback callback); typedef void (*RequestBatchUpdate)(int32_t contextId); typedef void (*ReloadApp)(int32_t contextId); typedef int32_t (*SetTimeout)(void* callbackContext, int32_t contextId, AsyncCallback callback, int32_t timeout); @@ -27,16 +41,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 @@ -53,12 +73,13 @@ struct MousePointer { double y; double change; }; -using SimulatePointer = void (*)(MousePointer**, int32_t length, int32_t pointer); +using SimulatePointer = void (*)(MousePointer*, int32_t length, int32_t pointer); using SimulateInputText = void (*)(NativeString* nativeString); -namespace webf { struct DartMethodPointer { - DartMethodPointer() = default; + DartMethodPointer() = delete; + explicit DartMethodPointer(const uint64_t* dart_methods, int32_t dartMethodsLength); + InvokeModule invokeModule{nullptr}; RequestBatchUpdate requestBatchUpdate{nullptr}; ReloadApp reloadApp{nullptr}; @@ -67,7 +88,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 +99,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 -WEBF_EXPORT -void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length); -#endif - -WEBF_EXPORT -std::shared_ptr getDartMethod(); - } // namespace webf #endif diff --git a/bridge/core/defined_properties.json5 b/bridge/core/defined_properties.json5 new file mode 100644 index 0000000000..de4c0d5e70 --- /dev/null +++ b/bridge/core/defined_properties.json5 @@ -0,0 +1,13 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "defined_properties" + } + ] + }, + "data": [ + // The data of defined_properties.json5 will be injected by code_generator scripts. + ] +} diff --git a/bridge/core/defined_properties_initializer.json5 b/bridge/core/defined_properties_initializer.json5 new file mode 100644 index 0000000000..47e37c0d5a --- /dev/null +++ b/bridge/core/defined_properties_initializer.json5 @@ -0,0 +1,13 @@ +{ + "metadata": { + "templates": [ + { + "template": "defined_properties_initializer", + "filename": "defined_properties_initializer" + } + ] + }, + "data": [ + // The data of defined_properties_initializer.json5 will be injected by code_generator scripts. + ] +} diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc new file mode 100644 index 0000000000..88dc9483f7 --- /dev/null +++ b/bridge/core/dom/character_data.cc @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "character_data.h" +#include "built_in_string.h" +#include "core/dom/document.h" +#include "qjs_character_data.h" + +namespace webf { + +void CharacterData::setData(const AtomicString& data, ExceptionState& exception_state) { + data_ = data; + + std::unique_ptr args_01 = stringToNativeString("data"); + std::unique_ptr args_02 = data.ToNativeString(); + + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kSetAttribute, std::move(args_01), + std::move(args_02), (void*)bindingObject()); +} + +std::string CharacterData::nodeValue() const { + return data_.ToStdString(); +} + +bool CharacterData::IsCharacterDataNode() const { + return true; +} + +void CharacterData::setNodeValue(const AtomicString& value, ExceptionState& exception_state) { + setData(!value.IsEmpty() ? value : built_in_string::kempty_string, exception_state); +} + +bool CharacterData::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSCharacterData::IsAttributeDefinedInternal(key) || Node::IsAttributeDefinedInternal(key); +} + +CharacterData::CharacterData(TreeScope& tree_scope, const AtomicString& text, Node::ConstructionType type) + : Node(tree_scope.GetDocument().GetExecutingContext(), &tree_scope, type), data_(text) { + assert(type == kCreateOther || type == kCreateText); +} + +} // namespace webf diff --git a/bridge/core/dom/character_data.d.ts b/bridge/core/dom/character_data.d.ts new file mode 100644 index 0000000000..f63d64b46c --- /dev/null +++ b/bridge/core/dom/character_data.d.ts @@ -0,0 +1,7 @@ +import {Node} from "./node"; + +export interface CharacterData extends Node { + 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..b7e99ebc69 --- /dev/null +++ b/bridge/core/dom/character_data.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CHARACTER_DATA_H +#define BRIDGE_CHARACTER_DATA_H + +#include "node.h" + +namespace webf { + +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, ExceptionState& exception_state); + + std::string nodeValue() const override; + bool IsCharacterDataNode() const override; + void setNodeValue(const AtomicString&, ExceptionState&) override; + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + protected: + CharacterData(TreeScope& tree_scope, const AtomicString& text, ConstructionType type); + + private: + AtomicString data_; +}; + +template <> +struct DowncastTraits { + static bool AllowFrom(const Node& node) { return node.IsCharacterDataNode(); } +}; + +} // namespace webf + +#endif // BRIDGE_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..85a83cfd54 --- /dev/null +++ b/bridge/core/dom/child_node_list.cc @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "child_node_list.h" +#include "bindings/qjs/cppgc/gc_visitor.h" + +namespace webf { + +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); +} + +bool ChildNodeList::NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state) { + int32_t index = std::stoi(key.ToStdString()); + return collection_index_cache_.NodeAt(*this, index); +} + +void ChildNodeList::NamedPropertyEnumerator(std::vector& names, ExceptionState& exception_state) { + uint32_t size = collection_index_cache_.NodeCount(*this); + for (int i = 0; i < size; i++) { + names.emplace_back(AtomicString(ctx(), std::to_string(i))); + } +} + +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 webf diff --git a/bridge/core/dom/child_node_list.h b/bridge/core/dom/child_node_list.h new file mode 100644 index 0000000000..77da088e13 --- /dev/null +++ b/bridge/core/dom/child_node_list.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2013, Opera Software ASA. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Opera Software ASA nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ +#define BRIDGE_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 webf { + +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; + + bool NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state) override; + void NamedPropertyEnumerator(std::vector& names, ExceptionState& exception_state) 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 webf + +#endif // BRIDGE_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..215a003473 --- /dev/null +++ b/bridge/core/dom/collection_index_cache.h @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2012,2013 Google Inc. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ +#define BRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ + +#include +#include +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "foundation/macros.h" + +namespace webf { + +template +class CollectionIndexCache { + WEBF_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); + + virtual 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 webf + +#endif // BRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ diff --git a/bridge/core/dom/collection_items_cache.h b/bridge/core/dom/collection_items_cache.h new file mode 100644 index 0000000000..6560517d9c --- /dev/null +++ b/bridge/core/dom/collection_items_cache.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2012,2013 Google Inc. All rights reserved. + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef BRIDGE_CORE_DOM_COLLECTION_ITEMS_CACHE_H_ +#define BRIDGE_CORE_DOM_COLLECTION_ITEMS_CACHE_H_ + +#include "collection_index_cache.h" + +namespace webf { + +template +class CollectionItemsCache : public CollectionIndexCache { + WEBF_DISALLOW_NEW(); + + typedef CollectionIndexCache Base; + + public: + CollectionItemsCache(); + ~CollectionItemsCache(); + + void Trace(GCVisitor* visitor) const override { + visitor->Trace(cached_list_); + Base::Trace(visitor); + } + + unsigned NodeCount(const Collection&); + NodeType* NodeAt(const Collection&, unsigned index); + void Invalidate() override; + + private: + bool list_valid_; + std::vector> cached_list_; +}; + +template +CollectionItemsCache::CollectionItemsCache() : list_valid_(false) {} + +template +CollectionItemsCache::~CollectionItemsCache() = default; + +template +void CollectionItemsCache::Invalidate() { + Base::Invalidate(); + if (list_valid_) { + cached_list_.Shrink(0); + list_valid_ = false; + } +} + +template +unsigned CollectionItemsCache::NodeCount(const Collection& collection) { + if (this->IsCachedNodeCountValid()) + return this->CachedNodeCount(); + + NodeType* current_node = collection.TraverseToFirst(); + unsigned current_index = 0; + while (current_node) { + cached_list_.push_back(current_node); + current_node = collection.TraverseForwardToOffset(current_index + 1, *current_node, current_index); + } + + this->SetCachedNodeCount(cached_list_.size()); + list_valid_ = true; + return this->CachedNodeCount(); +} + +template +inline NodeType* CollectionItemsCache::NodeAt(const Collection& collection, unsigned index) { + if (list_valid_) { + DCHECK(this->IsCachedNodeCountValid()); + return index < this->CachedNodeCount() ? cached_list_[index] : nullptr; + } + return Base::NodeAt(collection, index); +} + +} // namespace webf + +#endif // BRIDGE_CORE_DOM_COLLECTION_ITEMS_CACHE_H_ diff --git a/bridge/core/dom/comment.cc b/bridge/core/dom/comment.cc new file mode 100644 index 0000000000..592cfb14ab --- /dev/null +++ b/bridge/core/dom/comment.cc @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "comment.h" +#include "built_in_string.h" +#include "document.h" +#include "tree_scope.h" + +namespace webf { + +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 { + Node* copy = Create(factory); + std::unique_ptr args_01 = stringToNativeString(std::to_string(copy->eventTargetId())); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCloneNode, std::move(args_01), + nullptr); + return copy; +} + +} // namespace webf 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..dff2a35ae6 --- /dev/null +++ b/bridge/core/dom/comment.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_COMMENT_H +#define BRIDGE_COMMENT_H + +#include "character_data.h" + +namespace webf { + +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 webf + +#endif // BRIDGE_COMMENT_H diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc new file mode 100644 index 0000000000..4cdeaa66bf --- /dev/null +++ b/bridge/core/dom/container_node.cc @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "container_node.h" +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "core/html/html_all_collection.h" +#include "document.h" +#include "document_fragment.h" +#include "node_traversal.h" + +namespace webf { + +// Legacy impls due to limited time, should remove this func in the future. +std::vector ContainerNode::Children() { + std::vector elements; + uint32_t length = childNodes()->length(); + for (int i = 0; i < length; i++) { + auto* element = DynamicTo(childNodes()->item(i, ASSERT_NO_EXCEPTION())); + if (element) { + elements.emplace_back(element); + } + } + return elements; +} + +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); + NotifyNodeRemoved(*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); + NotifyNodeRemoved(*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); + NotifyNodeInsertedInternal(child); + } + } +} + +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(next_child.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::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; + node.InsertedInto(*this); + } +} + +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 webf diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h new file mode 100644 index 0000000000..0be129e360 --- /dev/null +++ b/bridge/core/dom/container_node.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_CONTAINER_NODE_H_ +#define BRIDGE_CORE_DOM_CONTAINER_NODE_H_ + +#include +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "node.h" +#include "node_list.h" + +namespace webf { + +class HTMLAllCollection; + +// 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; + + std::vector 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 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(); } + static bool AllowFrom(const EventTarget& event_target) { + return event_target.IsNode() && To(event_target).IsContainerNode(); + } +}; + +} // namespace webf + +#endif // BRIDGE_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..f5d67e1905 --- /dev/null +++ b/bridge/core/dom/document.cc @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "document.h" +#include "binding_call_methods.h" +#include "bindings/qjs/exception_message.h" +#include "core/dom/comment.h" +#include "core/dom/document_fragment.h" +#include "core/dom/element.h" +#include "core/dom/events/event_target.h" +#include "core/dom/text.h" +#include "core/frame/window.h" +#include "core/html/custom/widget_element.h" +#include "core/html/html_all_collection.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 "event_factory.h" +#include "foundation/ascii_types.h" +#include "foundation/native_value_converter.h" +#include "html_element_factory.h" +#include "qjs_document.h" + +namespace webf { + +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()); +} + +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; + } + + if (WidgetElement::IsValidName(name)) { + return MakeGarbageCollected(name, this); + } + + return MakeGarbageCollected(name, *this); +} + +Element* Document::createElement(const AtomicString& name, + const ScriptValue& options, + ExceptionState& exception_state) { + return createElement(name, exception_state); +} + +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); +} + +Comment* Document::createComment(const AtomicString& data, ExceptionState& exception_state) { + return Comment::Create(*this); +} + +Event* Document::createEvent(const AtomicString& type, ExceptionState& exception_state) { + return EventFactory::Create(GetExecutingContext(), type, nullptr); +} + +HTMLAllCollection* Document::all() { + return MakeGarbageCollected(this, CollectionType::kDocAll); +} + +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; +} + +Element* Document::querySelector(const AtomicString& selectors, ExceptionState& exception_state) { + NativeValue arguments[] = {NativeValueConverter::ToNativeValue(selectors)}; + NativeValue result = InvokeBindingMethod(binding_call_methods::kquerySelector, 1, arguments, exception_state); + if (exception_state.HasException()) { + return nullptr; + } + return NativeValueConverter>::FromNativeValue(ctx(), result); +} + +std::vector Document::querySelectorAll(const AtomicString& selectors, ExceptionState& exception_state) { + NativeValue arguments[] = {NativeValueConverter::ToNativeValue(selectors)}; + NativeValue result = InvokeBindingMethod(binding_call_methods::kquerySelectorAll, 1, arguments, exception_state); + if (exception_state.HasException()) { + return {}; + } + return NativeValueConverter>>::FromNativeValue(ctx(), result); +} + +Element* Document::getElementById(const AtomicString& id, ExceptionState& exception_state) { + NativeValue arguments[] = {NativeValueConverter::ToNativeValue(id)}; + NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementById, 1, arguments, exception_state); + if (exception_state.HasException()) { + return {}; + } + return NativeValueConverter>::FromNativeValue(ctx(), result); +} + +std::vector Document::getElementsByClassName(const AtomicString& class_name, + ExceptionState& exception_state) { + NativeValue arguments[] = {NativeValueConverter::ToNativeValue(class_name)}; + NativeValue result = + InvokeBindingMethod(binding_call_methods::kgetElementsByClassName, 1, arguments, exception_state); + if (exception_state.HasException()) { + return {}; + } + return NativeValueConverter>>::FromNativeValue(ctx(), result); +} + +std::vector Document::getElementsByTagName(const AtomicString& tag_name, ExceptionState& exception_state) { + NativeValue arguments[] = {NativeValueConverter::ToNativeValue(tag_name)}; + NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementsByTagName, 1, arguments, exception_state); + if (exception_state.HasException()) { + return {}; + } + return NativeValueConverter>>::FromNativeValue(ctx(), result); +} + +std::vector Document::getElementsByName(const AtomicString& name, ExceptionState& exception_state) { + NativeValue arguments[] = {NativeValueConverter::ToNativeValue(name)}; + NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementsByName, 1, arguments, exception_state); + if (exception_state.HasException()) { + return {}; + } + return NativeValueConverter>>::FromNativeValue(ctx(), result); +} + +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; +} + +HTMLHtmlElement* Document::documentElement() const { + for (HTMLElement* child = Traversal::FirstChild(*this); child; + child = Traversal::NextSibling(*child)) { + if (IsA(*child)) + return DynamicTo(child); + } + + return nullptr; +} + +// 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::SetWindowAttributeEventListener(const AtomicString& event_type, + const std::shared_ptr& listener, + ExceptionState& exception_state) { + Window* window = GetExecutingContext()->window(); + if (!window) + return; + window->SetAttributeEventListener(event_type, listener, exception_state); +} + +std::shared_ptr Document::GetWindowAttributeEventListener(const AtomicString& event_type) { + Window* window = GetExecutingContext()->window(); + if (!window) + return nullptr; + return window->GetAttributeEventListener(event_type); +} + +bool Document::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSDocument::IsAttributeDefinedInternal(key) || Node::IsAttributeDefinedInternal(key); +} + +void Document::Trace(GCVisitor* visitor) const { + script_animation_controller_.Trace(visitor); + ContainerNode::Trace(visitor); +} + +} // namespace webf diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts new file mode 100644 index 0000000000..0a562d97db --- /dev/null +++ b/bridge/core/dom/document.d.ts @@ -0,0 +1,36 @@ +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"; +import {Event} from "./events/event"; +import {HTMLAllCollection} from "../html/html_all_collection"; + +interface Document extends Node { + readonly all: HTMLAllCollection; + body: HTMLBodyElement | null; + cookie: DartImpl; + readonly head: HTMLHeadElement | null; + readonly documentElement: HTMLHtmlElement | null; + // Legacy impl: get the polyfill implements from global object. + readonly location: any; + + createElement(tagName: string, options?: any): Element; + createTextNode(value: string): Text; + createDocumentFragment(): DocumentFragment; + createComment(data?: string): Comment; + createEvent(event_type: string): Event; + + getElementById(id: string): Element | null; + getElementsByClassName(className: string) : Element[]; + getElementsByTagName(tagName: string): Element[]; + getElementsByName(name: string): Element[]; + + querySelector(selectors: string): Element | null; + querySelectorAll(selectors: string): Element[]; + + new(): Document; +} diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h new file mode 100644 index 0000000000..217a53b302 --- /dev/null +++ b/bridge/core/dom/document.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_DOCUMENT_H +#define BRIDGE_DOCUMENT_H + +#include "bindings/qjs/cppgc/local_handle.h" +#include "container_node.h" +#include "scripted_animation_controller.h" +#include "tree_scope.h" + +namespace webf { + +class HTMLBodyElement; +class HTMLHeadElement; +class HTMLHtmlElement; +class Text; +class Comment; + +enum NodeListInvalidationType : int { + kDoNotInvalidateOnAttributeChanges = 0, + kInvalidateOnClassAttrChange, + kInvalidateOnIdNameAttrChange, + kInvalidateOnNameAttrChange, + kInvalidateOnForAttrChange, + kInvalidateForFormControls, + kInvalidateOnHRefAttrChange, + kInvalidateOnAnyAttrChange, +}; +const int kNumNodeListInvalidationTypes = kInvalidateOnAnyAttrChange + 1; + +// 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); + Element* createElement(const AtomicString& name, const ScriptValue& options, ExceptionState& exception_state); + Text* createTextNode(const AtomicString& value, ExceptionState& exception_state); + DocumentFragment* createDocumentFragment(ExceptionState& exception_state); + Comment* createComment(ExceptionState& exception_state); + Comment* createComment(const AtomicString& data, ExceptionState& exception_state); + Event* createEvent(const AtomicString& type, ExceptionState& exception_state); + HTMLAllCollection* all(); + + [[nodiscard]] std::string nodeName() const override; + [[nodiscard]] std::string nodeValue() const override; + [[nodiscard]] NodeType nodeType() const override; + [[nodiscard]] bool ChildTypeAllowed(NodeType) const override; + + Element* querySelector(const AtomicString& selectors, ExceptionState& exception_state); + std::vector querySelectorAll(const AtomicString& selectors, ExceptionState& exception_state); + + Element* getElementById(const AtomicString& id, ExceptionState& exception_state); + std::vector getElementsByClassName(const AtomicString& class_name, ExceptionState& exception_state); + std::vector getElementsByTagName(const AtomicString& tag_name, ExceptionState& exception_state); + std::vector getElementsByName(const AtomicString& name, ExceptionState& exception_state); + + // 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; + + // "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); + + // Helper functions for forwarding LocalDOMWindow event related tasks to the + // LocalDOMWindow if it exists. + void SetWindowAttributeEventListener(const AtomicString& event_type, + const std::shared_ptr& listener, + ExceptionState& exception_state); + std::shared_ptr GetWindowAttributeEventListener(const AtomicString& event_type); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + void Trace(GCVisitor* visitor) const override; + + private: + int node_count_{0}; + ScriptAnimationController script_animation_controller_; +}; + +template <> +struct DowncastTraits { + static bool AllowFrom(const Node& node) { return node.IsDocumentNode(); } +}; + +} // namespace webf + +#endif // BRIDGE_DOCUMENT_H diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc new file mode 100644 index 0000000000..58c86d355c --- /dev/null +++ b/bridge/core/dom/document_fragment.cc @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "document_fragment.h" +#include "document.h" +#include "events/event_target.h" + +namespace webf { + +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) { + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateDocumentFragment, + (void*)bindingObject()); +} + +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); + std::unique_ptr args_01 = stringToNativeString(std::to_string(clone->eventTargetId())); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCloneNode, std::move(args_01), + nullptr); + return clone; +} + +bool DocumentFragment::ChildTypeAllowed(NodeType type) const { + switch (type) { + case kElementNode: + case kCommentNode: + case kTextNode: + return true; + default: + return false; + } +} + +} // namespace webf 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..7c75ceb52f --- /dev/null +++ b/bridge/core/dom/document_fragment.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_DOCUMENT_FRAGMENT_H +#define BRIDGE_DOCUMENT_FRAGMENT_H + +#include "container_node.h" + +namespace webf { + +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 webf + +#endif // BRIDGE_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..9cf65cf162 --- /dev/null +++ b/bridge/core/dom/document_test.cc @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "gtest/gtest.h" +#include "webf_test_env.h" + +using namespace webf; + +TEST(Document, createElement) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::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) { + WEBF_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; + webf::WebFPage::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) { + WEBF_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; + webf::WebFPage::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; + webf::WebFPage::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) { + WEBF_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; + webf::WebFPage::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) { + WEBF_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; + webf::WebFPage::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) { + WEBF_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; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = false; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_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) { + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + + webf::WebFPage* 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; +} + +TEST(document, all) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "3 "); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = "console.log(document.all.length, document.all[0])"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc new file mode 100644 index 0000000000..bfba99b8f7 --- /dev/null +++ b/bridge/core/dom/element.cc @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#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 "element_attribute_names.h" +#include "foundation/native_value_converter.h" +#include "html_element_type_helper.h" +#include "qjs_element.h" +#include "text.h" + +namespace webf { + +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, exception_state); +} + +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, exception_state); + 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(), value); + } +} + +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->hasLeft() ? options->left() : 0.0), + NativeValueConverter::ToNativeValue(options->hasTop() ? options->top() : 0.0), + }; + 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->hasLeft() ? options->left() : 0.0), + NativeValueConverter::ToNativeValue(options->hasTop() ? options->top() : 0.0), + }; + 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_.ToUpperIfNecessary().ToStdString(); +} + +std::string Element::nodeNameLowerCase() const { + return tag_name_.ToStdString(); +} + +std::vector Element::getElementsByClassName(const AtomicString& class_name, ExceptionState& exception_state) { + NativeValue arguments[] = {NativeValueConverter::ToNativeValue(class_name)}; + NativeValue result = + InvokeBindingMethod(binding_call_methods::kgetElementsByClassName, 1, arguments, exception_state); + if (exception_state.HasException()) { + return {}; + } + return NativeValueConverter>>::FromNativeValue(ctx(), result); +} + +std::vector Element::getElementsByTagName(const AtomicString& tag_name, ExceptionState& exception_state) { + NativeValue arguments[] = {NativeValueConverter::ToNativeValue(tag_name)}; + NativeValue result = InvokeBindingMethod(binding_call_methods::kgetElementsByTagName, 1, arguments, exception_state); + if (exception_state.HasException()) { + return {}; + } + return NativeValueConverter>>::FromNativeValue(ctx(), result); +} + +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_); + } + if (other.element_data_ != nullptr) { + EnsureElementData().CopyWith(other.element_data_.get()); + } +} + +bool Element::HasEquivalentAttributes(const Element& other) const { + return attributes_ != nullptr && other.attributes_ != nullptr && other.attributes_->IsEquivalent(*attributes_); +} + +bool Element::IsWidgetElement() const { + return false; +} + +bool Element::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSElement::IsAttributeDefinedInternal(key) || Node::IsAttributeDefinedInternal(key); +} + +void Element::Trace(GCVisitor* visitor) const { + visitor->Trace(attributes_); + visitor->Trace(cssom_wrapper_); + ContainerNode::Trace(visitor); +} + +ElementData& Element::EnsureElementData() const { + if (element_data_ == nullptr) { + element_data_ = std::make_unique(); + } + return *element_data_; +} + +Node* Element::Clone(Document& factory, CloneChildrenFlag flag) const { + Element* copy; + if (flag == CloneChildrenFlag::kSkip) { + copy = &CloneWithoutChildren(&factory); + } else { + copy = &CloneWithChildren(flag, &factory); + } + + std::unique_ptr args_01 = stringToNativeString(std::to_string(copy->eventTargetId())); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCloneNode, std::move(args_01), + nullptr); + + 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(); +} + +std::string Element::outerHTML() { + std::string s = "<" + nodeNameLowerCase(); + + // 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 webf diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts new file mode 100644 index 0000000000..f694432919 --- /dev/null +++ b/bridge/core/dom/element.d.ts @@ -0,0 +1,67 @@ +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"; +import {ParentNode} from "./parent_node"; + +interface Element extends Node, ParentNode { + id: DartImpl; + className: DartImpl; + class: DartImpl; + name: DartImpl; + readonly attributes: ElementAttributes; + readonly style: CSSStyleDeclaration; + readonly clientHeight: DartImpl; + readonly clientLeft: DartImpl; + readonly clientTop: DartImpl; + readonly clientWidth: DartImpl; + readonly outerHTML: string; + innerHTML: string; + readonly ownerDocument: Document; + scrollLeft: DartImpl; + scrollTop: DartImpl; + readonly scrollWidth: DartImpl; + readonly scrollHeight: DartImpl; + /** + * 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; + + /** + * Indicating whether the specified element has the specified attribute or not. + */ + hasAttribute(qualifiedName: string): boolean; + + // CSSOM View Module + // https://drafts.csswg.org/cssom-view/#extension-to-the-element-interface + getBoundingClientRect(): BoundingClientRect; + + getElementsByClassName(className: string) : Element[]; + getElementsByTagName(tagName: string): Element[]; + + 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; + + // Export the target element's rendering content to PNG. + // WebF 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..d34dbb8b6b --- /dev/null +++ b/bridge/core/dom/element.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_ELEMENT_H +#define BRIDGE_ELEMENT_H + +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "bindings/qjs/script_promise.h" +#include "container_node.h" +#include "core/css/legacy/css_style_declaration.h" +#include "element_data.h" +#include "legacy/bounding_client_rect.h" +#include "legacy/element_attributes.h" +#include "parent_node.h" +#include "qjs_scroll_to_options.h" + +namespace webf { + +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); + + 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_.ToUpperSlow(); } + std::string nodeName() const override; + std::string nodeNameLowerCase() const; + + std::vector getElementsByClassName(const AtomicString& class_name, ExceptionState& exception_state); + std::vector getElementsByTagName(const AtomicString& tag_name, ExceptionState& exception_state); + + 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) {} + virtual bool IsWidgetElement() const; + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + void Trace(GCVisitor* visitor) const override; + + protected: + const ElementData* GetElementData() const { return element_data_.get(); } + ElementData& EnsureElementData() const; + + 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); + + mutable std::unique_ptr element_data_; + Member attributes_; + Member cssom_wrapper_; + AtomicString tag_name_ = AtomicString::Empty(); +}; + +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(); } + static bool AllowFrom(const BindingObject& binding_object) { + return binding_object.IsEventTarget() && To(binding_object).IsNode() && + To(binding_object).IsElementNode(); + } +}; + +} // namespace webf + +#endif // BRIDGE_ELEMENT_H diff --git a/bridge/core/dom/element_attribute_names.json5 b/bridge/core/dom/element_attribute_names.json5 new file mode 100644 index 0000000000..bc4b91325d --- /dev/null +++ b/bridge/core/dom/element_attribute_names.json5 @@ -0,0 +1,14 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "element_attribute_names" + } + ] + }, + "data": [ + "id", + "className" + ] +} diff --git a/bridge/core/dom/element_data.cc b/bridge/core/dom/element_data.cc new file mode 100644 index 0000000000..39f5e7471f --- /dev/null +++ b/bridge/core/dom/element_data.cc @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "element_data.h" + +namespace webf { + +void ElementData::CopyWith(ElementData* other) {} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/dom/element_data.h b/bridge/core/dom/element_data.h new file mode 100644 index 0000000000..3787289a41 --- /dev/null +++ b/bridge/core/dom/element_data.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_DOM_ELEMENT_DATA_H_ +#define WEBF_CORE_DOM_ELEMENT_DATA_H_ + +#include "bindings/qjs/atomic_string.h" + +namespace webf { + +class ElementData { + public: + void CopyWith(ElementData* other); + + private: + AtomicString class_; +}; + +} // namespace webf + +#endif // WEBF_CORE_DOM_ELEMENT_DATA_H_ diff --git a/bridge/bindings/qjs/dom/element_test.cc b/bridge/core/dom/element_test.cc similarity index 79% rename from bridge/bindings/qjs/dom/element_test.cc rename to bridge/core/dom/element_test.cc index 17a7d87f8a..753b55090e 100644 --- a/bridge/bindings/qjs/dom/element_test.cc +++ b/bridge/core/dom/element_test.cc @@ -3,10 +3,10 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#include "event_target.h" +#include "core/dom/legacy/bounding_client_rect.h" #include "gtest/gtest.h" -#include "page.h" #include "webf_test_env.h" +using namespace webf; TEST(Element, setAttribute) { bool static errorCalled = false; @@ -19,7 +19,7 @@ TEST(Element, setAttribute) { WEBF_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);" @@ -41,7 +41,7 @@ TEST(Element, getAttribute) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "let string = 'helloworld';" @@ -61,58 +61,68 @@ TEST(Element, getAttribute) { TEST(Element, setAttributeWithHTML) { bool static errorCalled = false; bool static logCalled = false; - webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + webf::WebFPage::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) { WEBF_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); } -TEST(Element, style) { +TEST(Element, outerHTML) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; - EXPECT_STREQ(message.c_str(), "true false"); + EXPECT_STREQ(message.c_str(), + "
"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); - const char* code = "console.log('borderTop' in document.body.style, 'borderXXX' in document.body.style)"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); + auto context = bridge->GetExecutingContext(); + std::string code = R"( +const div = document.createElement('div'); +div.style.width = '100px'; +div.style.height = '100px'; +div.setAttribute('attr-key', 'attr-value'); + +document.body.appendChild(div); +console.log(div.outerHTML, div.innerHTML, document.body.innerHTML); +)"; + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(errorCalled, false); } -TEST(Element, instanceofNode) { +TEST(Element, style) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; - EXPECT_STREQ(message.c_str(), "true"); + EXPECT_STREQ(message.c_str(), "true false"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); - const char* code = - "let div = document.createElement('div');" - "console.log(div instanceof Node)"; + const char* code = "console.log('borderTop' in document.body.style, 'borderXXX' in document.body.style)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); } -TEST(Element, instanceofEventTarget) { +TEST(Element, instanceofNode) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -123,41 +133,33 @@ TEST(Element, instanceofEventTarget) { WEBF_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)"; + "console.log(div instanceof Node)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } -TEST(Element, stringifyBoundingClientRect) { - using namespace webf::binding::qjs; - +TEST(Element, instanceofEventTarget) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::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(), "true"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); - - 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); - - const char* code = "console.log(JSON.stringify(boundingClient))"; + auto context = bridge->GetExecutingContext(); + const char* code = + "let div = document.createElement('div');" + "console.log(div instanceof EventTarget)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); -} +} \ No newline at end of file diff --git a/bridge/core/dom/element_traversal.h b/bridge/core/dom/element_traversal.h new file mode 100644 index 0000000000..870ba67b13 --- /dev/null +++ b/bridge/core/dom/element_traversal.h @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2013, Opera Software ASA. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Opera Software ASA nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ +#define BRIDGE_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 webf { + +class HasTagName { + WEBF_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 { + WEBF_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 webf + +#endif // BRIDGE_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..0d0513ae20 --- /dev/null +++ b/bridge/core/dom/empty_node_list.cc @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "empty_node_list.h" +#include "core/dom/node.h" + +namespace webf { + +EmptyNodeList::EmptyNodeList(Node* root_node) : owner_(root_node), NodeList(root_node->ctx()) {} + +void EmptyNodeList::Trace(GCVisitor* visitor) const {} + +bool EmptyNodeList::NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state) { + return false; +} + +void EmptyNodeList::NamedPropertyEnumerator(std::vector<AtomicString>& names, ExceptionState& exception_state) {} + +Node* EmptyNodeList::VirtualOwnerNode() const { + return &OwnerNode(); +} + +} // namespace webf diff --git a/bridge/core/dom/empty_node_list.h b/bridge/core/dom/empty_node_list.h new file mode 100644 index 0000000000..a273f006f4 --- /dev/null +++ b/bridge/core/dom/empty_node_list.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ +#define BRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ + +#include <vector> +#include "node_list.h" + +namespace webf { + +class ExceptionState; +class AtomicString; + +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 NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state) override; + void NamedPropertyEnumerator(std::vector<AtomicString>& names, ExceptionState& exception_state) override; + + bool IsEmptyNodeList() const override { return true; } + Node* VirtualOwnerNode() const override; + + Node* owner_; +}; + +} // namespace webf + +#endif // BRIDGE_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..649a2185fc --- /dev/null +++ b/bridge/core/dom/events/custom_event.cc @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "custom_event.h" +#include "native_value_converter.h" + +namespace webf { + +CustomEvent* CustomEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { + return MakeGarbageCollected<CustomEvent>(context, type, exception_state); +} + +CustomEvent* CustomEvent::Create(ExecutingContext* context, + const AtomicString& type, + NativeCustomEvent* native_custom_event) { + return MakeGarbageCollected<CustomEvent>(context, type, native_custom_event); +} + +CustomEvent* CustomEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<CustomEventInit>& initialize, + ExceptionState& exception_state) { + return MakeGarbageCollected<CustomEvent>(context, type, initialize, exception_state); +} + +CustomEvent::CustomEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : Event(context, type) {} + +CustomEvent::CustomEvent(ExecutingContext* context, const AtomicString& type, NativeCustomEvent* native_custom_event) + : Event(context, type, &native_custom_event->native_event), + detail_(ScriptValue(ctx(), *native_custom_event->detail)) {} + +CustomEvent::CustomEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<CustomEventInit>& initialize, + ExceptionState& exception_state) + : Event(context, type), detail_(initialize->detail()) {} + +ScriptValue CustomEvent::detail() const { + return detail_; +} + +void CustomEvent::initCustomEvent(const AtomicString& type, ExceptionState& exception_state) { + initCustomEvent(type, false, false, ScriptValue::Empty(ctx()), exception_state); +} +void CustomEvent::initCustomEvent(const AtomicString& type, bool can_bubble, ExceptionState& exception_state) { + initCustomEvent(type, can_bubble, false, ScriptValue::Empty(ctx()), exception_state); +} +void CustomEvent::initCustomEvent(const AtomicString& type, + bool can_bubble, + bool cancelable, + ExceptionState& exception_state) { + initCustomEvent(type, can_bubble, cancelable, ScriptValue::Empty(ctx()), exception_state); +} +void CustomEvent::initCustomEvent(const AtomicString& type, + bool can_bubble, + bool cancelable, + const ScriptValue& detail, + ExceptionState& exception_state) { + initEvent(type, can_bubble, cancelable, exception_state); + if (!IsBeingDispatched() && !detail.IsEmpty()) { + detail_ = detail; + } +} + +bool CustomEvent::IsCustomEvent() const { + return true; +} + +} // namespace webf diff --git a/bridge/core/dom/events/custom_event.d.ts b/bridge/core/dom/events/custom_event.d.ts new file mode 100644 index 0000000000..a8a7bf7919 --- /dev/null +++ b/bridge/core/dom/events/custom_event.d.ts @@ -0,0 +1,11 @@ +/** Events providing information related to animations. */ +import {Event} from "./event"; +import {CustomEventInit} from "./custom_event_init"; + +interface CustomEvent extends Event { + readonly detail: any; + + initCustomEvent(type: string, canBubble?: boolean, cancelable?: boolean, detail?: any): void; + + new(type: string, init?: CustomEventInit): CustomEvent; +} \ No newline at end of file diff --git a/bridge/core/dom/events/custom_event.h b/bridge/core/dom/events/custom_event.h new file mode 100644 index 0000000000..f83effbc5b --- /dev/null +++ b/bridge/core/dom/events/custom_event.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CUSTOM_EVENT_H +#define BRIDGE_CUSTOM_EVENT_H + +#include "event.h" +#include "qjs_custom_event_init.h" + +namespace webf { + +struct NativeCustomEvent { + NativeEvent native_event; + NativeValue* detail{nullptr}; +}; + +class CustomEvent final : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = CustomEvent*; + + static CustomEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + static CustomEvent* Create(ExecutingContext* context, + const AtomicString& type, + NativeCustomEvent* native_custom_event); + static CustomEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<CustomEventInit>& initialize, + ExceptionState& exception_state); + + CustomEvent() = delete; + explicit CustomEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + explicit CustomEvent(ExecutingContext* context, const AtomicString& type, NativeCustomEvent* native_custom_event); + explicit CustomEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<CustomEventInit>& initialize, + ExceptionState& exception_state); + + ScriptValue detail() const; + + void initCustomEvent(const AtomicString& type, ExceptionState& exception_state); + void initCustomEvent(const AtomicString& type, bool can_bubble, ExceptionState& exception_state); + void initCustomEvent(const AtomicString& type, bool can_bubble, bool cancelable, ExceptionState& exception_state); + void initCustomEvent(const AtomicString& type, + bool can_bubble, + bool cancelable, + const ScriptValue& detail, + ExceptionState& exception_state); + + bool IsCustomEvent() const override; + + private: + ScriptValue detail_; +}; + +template <> +struct DowncastTraits<CustomEvent> { + static bool AllowFrom(const Event& event) { return event.IsCustomEvent(); } +}; + +} // namespace webf + +#endif // BRIDGE_CUSTOM_EVENT_H diff --git a/bridge/core/dom/events/custom_event_init.d.ts b/bridge/core/dom/events/custom_event_init.d.ts new file mode 100644 index 0000000000..0e7f6ed06c --- /dev/null +++ b/bridge/core/dom/events/custom_event_init.d.ts @@ -0,0 +1,7 @@ +import { EventInit } from "./event_init"; + +// @ts-ignore +@Dictionary() +export interface CustomEventInit extends EventInit { + detail?: any; +} diff --git a/bridge/bindings/qjs/dom/custom_event_test.cc b/bridge/core/dom/events/custom_event_test.cc similarity index 92% rename from bridge/bindings/qjs/dom/custom_event_test.cc rename to bridge/core/dom/events/custom_event_test.cc index fe92a6d4c8..74c7e0180e 100644 --- a/bridge/bindings/qjs/dom/custom_event_test.cc +++ b/bridge/core/dom/events/custom_event_test.cc @@ -5,9 +5,10 @@ #include "event_target.h" #include "gtest/gtest.h" -#include "page.h" #include "webf_test_env.h" +using namespace webf; + TEST(CustomEvent, instanceofEvent) { bool static errorCalled = false; bool static logCalled = false; @@ -19,7 +20,7 @@ TEST(CustomEvent, instanceofEvent) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let customEvent = new CustomEvent('abc', { detail: 'helloworld'});" "console.log(customEvent instanceof Event);"; diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc new file mode 100644 index 0000000000..650fe5154d --- /dev/null +++ b/bridge/core/dom/events/event.cc @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "event.h" +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "core/executing_context.h" +#include "event_target.h" + +namespace webf { + +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) + : Event(context, + type, + init->bubbles() ? Bubbles::kYes : Bubbles::kNo, + init->cancelable() ? Cancelable::kYes : Cancelable::kNo, + init->composed() ? ComposedMode::kComposed : ComposedMode::kScoped, + std::chrono::system_clock::now().time_since_epoch().count()) {} + +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) {} + +Event::Event(ExecutingContext* context, const AtomicString& event_type, NativeEvent* native_event) + : ScriptWrappable(context->ctx()), + type_(event_type), + bubbles_(native_event->bubbles), + composed_(native_event->composed), + cancelable_(native_event->cancelable), + time_stamp_(native_event->timeStamp), + default_prevented_(native_event->defaultPrevented), + propagation_stopped_(false), + immediate_propagation_stopped_(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), +#if ANDROID_32_BIT + target_( + DynamicTo<EventTarget>(BindingObject::From(reinterpret_cast<NativeBindingObject*>(native_event->target)))), + current_target_(DynamicTo<EventTarget>( + BindingObject::From(reinterpret_cast<NativeBindingObject*>(native_event->currentTarget)))) { +} +#else + target_(DynamicTo<EventTarget>(BindingObject::From(native_event->target))), + current_target_(DynamicTo<EventTarget>(BindingObject::From(native_event->currentTarget))) { +} +#endif + +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::IsCloseEvent() const { + return false; +} + +bool Event::IsCustomEvent() const { + return false; +} + +bool Event::IsTransitionEvent() const { + return false; +} + +bool Event::IsAnimationEvent() const { + return false; +} + +bool Event::IsMessageEvent() const { + return false; +} + +bool Event::IsPopstateEvent() const { + return false; +} + +bool Event::IsIntersectionchangeEvent() const { + return false; +} + +bool Event::IsDragEvent() const { + return false; +} + +bool Event::IsBeforeUnloadEvent() const { + return false; +} + +bool Event::IsErrorEvent() const { + return false; +} + +bool Event::IsPromiseRejectionEvent() 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 { + visitor->Trace(target_); + visitor->Trace(current_target_); +} + +} // namespace webf 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..9b6dd166f2 --- /dev/null +++ b/bridge/core/dom/events/event.h @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_EVENT_H +#define BRIDGE_EVENT_H + +#include <cinttypes> +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/cppgc/member.h" +#include "bindings/qjs/script_wrappable.h" +#include "core/dom/events/event_target.h" +#include "core/executing_context.h" +#include "foundation/native_string.h" +#include "qjs_event_init.h" + +namespace webf { + +class EventTarget; +class ExceptionState; +struct NativeBindingObject; + +// 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 composed{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 composed{0}; + int64_t timeStamp{0}; + int64_t defaultPrevented{0}; + // The pointer address of target EventTargetInstance object. + NativeBindingObject* target{nullptr}; + // The pointer address of current target EventTargetInstance object. + NativeBindingObject* currentTarget{nullptr}; +}; +#endif + +struct RawEvent : public DartReadable { + uint64_t* bytes; + int64_t length; + int8_t is_custom_event; +}; + +template <typename T> +inline T* toNativeEvent(RawEvent* raw_event) { + // NativeEvent members are memory aligned corresponding to NativeEvent. + // So we can reinterpret_cast raw bytes pointer to NativeEvent type directly. + return reinterpret_cast<T*>(raw_event->bytes); +} + +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, 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); + }; + + Event() = delete; + 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); + explicit Event(ExecutingContext* context, const AtomicString& event_type, NativeEvent* native_event); + + 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() const { 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; + virtual bool IsCloseEvent() const; + virtual bool IsCustomEvent() const; + virtual bool IsTransitionEvent() const; + virtual bool IsAnimationEvent() const; + virtual bool IsMessageEvent() const; + virtual bool IsPopstateEvent() const; + virtual bool IsIntersectionchangeEvent() const; + + // Drag events are a subset of mouse events. + virtual bool IsDragEvent() const; + + virtual bool IsBeforeUnloadEvent() const; + virtual bool IsErrorEvent() const; + virtual bool IsPromiseRejectionEvent() 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; + + Member<EventTarget> target_; + Member<EventTarget> current_target_; +}; + +} // namespace webf + +#endif // BRIDGE_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..be4db43a8e --- /dev/null +++ b/bridge/core/dom/events/event_listener.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ +#define BRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ + +#include "core/executing_context.h" + +namespace webf { + +class JSBasedEventListener; +class Event; + +// 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 webf + +#endif // BRIDGE_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..572679a272 --- /dev/null +++ b/bridge/core/dom/events/event_listener_map.cc @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "event_listener_map.h" + +namespace webf { + +EventListenerMap::EventListenerMap() {} + +static bool AddListenerToVector(EventListenerVector* vector, + const std::shared_ptr<EventListener>& listener, + const std::shared_ptr<AddEventListenerOptions>& options, + RegisteredEventListener* registered_event_listener, + uint32_t* listener_count) { + *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); + *listener_count = vector->size(); + 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, + uint32_t* listener_count) { + // 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); + *listener_count = listener_vector->size(); + + 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, + uint32_t* listener_count) { + for (const auto& entry : entries_) { + if (entry.first == event_type) + return AddListenerToVector(entry.second.get(), listener, options, registered_event_listener, listener_count); + } + + entries_.emplace_back(event_type, std::make_unique<EventListenerVector>()); + return AddListenerToVector(entries_.back().second.get(), listener, options, registered_event_listener, + listener_count); +} + +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, + uint32_t* listener_count) { + 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, listener_count); + if (entries_[i].second->empty()) { + entries_.erase(entries_.begin() + i); + } + return was_removed; + } + } + + return false; +} + +EventListenerVector* EventListenerMap::Find(const AtomicString& event_type) const { + 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 webf 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..f7838b55fd --- /dev/null +++ b/bridge/core/dom/events/event_listener_map.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ +#define BRIDGE_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 webf { + +class AddEventListenerOptions; +class EventListenerOptions; + +using EventListenerVector = std::vector<RegisteredEventListener>; + +class EventListenerMap final { + WEBF_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, + uint32_t* listener_count); + 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, + uint32_t* listener_count); + EventListenerVector* Find(const AtomicString& event_type) const; + + 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 webf + +#endif // BRIDGE_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..f98f15ff4c --- /dev/null +++ b/bridge/core/dom/events/event_target.cc @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "event_target.h" +#include <cstdint> +#include "binding_call_methods.h" +#include "bindings/qjs/converter_impl.h" +#include "event_factory.h" +#include "native_value_converter.h" +#include "qjs_add_event_listener_options.h" +#include "qjs_event_target.h" + +#define PROPAGATION_STOPPED 1 +#define PROPAGATION_CONTINUE 0 + +#if UNIT_TEST +#include "webf_test_env.h" +#endif + +namespace webf { + +struct EventDispatchResult : public DartReadable { + bool canceled{false}; + bool propagationStopped{false}; +}; + +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() { +#if UNIT_TEST + // Callback to unit test specs before eventTarget finalized. + if (TEST_getEnv(GetExecutingContext()->uniqueId())->on_event_target_disposed != nullptr) { + TEST_getEnv(GetExecutingContext()->uniqueId())->on_event_target_disposed(this); + } +#endif + + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, + bindingObject()); +} + +EventTarget::EventTarget(ExecutingContext* context) + : BindingObject(context), ScriptWrappable(context->ctx()), event_target_id_(global_event_target_id++) {} + +EventTarget::EventTarget(ExecutingContext* context, NativeBindingObject* native_binding_object) + : BindingObject(context, native_binding_object), + ScriptWrappable(context->ctx()), + event_target_id_(global_event_target_id++) {} + +Node* EventTarget::ToNode() { + return nullptr; +} + +bool EventTarget::addEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& event_listener, + const std::shared_ptr<AddEventListenerOptions>& options, + ExceptionState& exception_state) { + if (options == nullptr) { + return AddEventListenerInternal(event_type, event_listener, AddEventListenerOptions::Create()); + } + 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::SetAttributeEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& listener, + ExceptionState& exception_state) { + RegisteredEventListener* registered_listener = GetAttributeRegisteredEventListener(event_type); + if (!listener) { + if (registered_listener) + removeEventListener(event_type, registered_listener->Callback(), exception_state); + return false; + } + if (registered_listener) { + registered_listener->SetCallback(listener); + return true; + } + return addEventListener(event_type, listener, exception_state); +} + +std::shared_ptr<EventListener> EventTarget::GetAttributeEventListener(const AtomicString& event_type) { + RegisteredEventListener* registered_listener = GetAttributeRegisteredEventListener(event_type); + if (registered_listener) + return registered_listener->Callback(); + return nullptr; +} + +EventListenerVector* EventTarget::GetEventListeners(const AtomicString& event_type) { + EventTargetData* data = GetEventTargetData(); + if (!data) + return nullptr; + return data->event_listener_map.Find(event_type); +} + +bool EventTarget::IsEventTarget() const { + return true; +} + +bool EventTarget::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSEventTarget::IsAttributeDefinedInternal(key); +} + +void EventTarget::Trace(GCVisitor* visitor) const { + ScriptWrappable::Trace(visitor); + BindingObject::Trace(visitor); +} + +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; + uint32_t listener_count = 0; + bool added = EnsureEventTargetData().event_listener_map.Add(event_type, listener, options, ®istered_listener, + &listener_count); + + if (added && listener_count == 1) { + GetExecutingContext()->uiCommandBuffer()->addCommand(event_target_id_, UICommand::kAddEvent, + std::move(event_type.ToNativeString()), nullptr); + } + + 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; + + uint32_t listener_count = UINT32_MAX; + if (!d->event_listener_map.Remove(event_type, listener, options, &index_of_removed_listener, ®istered_listener, + &listener_count)) + 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; + } + } + + if (listener_count == 0) { + GetExecutingContext()->uiCommandBuffer()->addCommand(event_target_id_, UICommand::kRemoveEvent, + std::move(event_type.ToNativeString()), nullptr); + } + + 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(const NativeValue* native_method, + int32_t argc, + const NativeValue* argv) { + MemberMutationScope mutation_scope{GetExecutingContext()}; + AtomicString method = NativeValueConverter<NativeTypeString>::FromNativeValue(ctx(), *native_method); + + if (method == binding_call_methods::kdispatchEvent) { + return HandleDispatchEventFromDart(argc, argv); + } + + return Native_NewNull(); +} + +NativeValue EventTarget::HandleDispatchEventFromDart(int32_t argc, const NativeValue* argv) { + assert(argc == 2); + AtomicString event_type = NativeValueConverter<NativeTypeString>::FromNativeValue(ctx(), argv[0]); + RawEvent* raw_event = NativeValueConverter<NativeTypePointer<RawEvent>>::FromNativeValue(argv[1]); + + Event* event = EventFactory::Create(GetExecutingContext(), event_type, raw_event); + ExceptionState exception_state; + event->SetTrusted(false); + event->SetEventPhase(Event::kAtTarget); + DispatchEventResult dispatch_result = FireEventListeners(*event, exception_state); + event->SetEventPhase(0); + + if (exception_state.HasException()) { + JSValue error = JS_GetException(ctx()); + GetExecutingContext()->ReportError(error); + JS_FreeValue(ctx(), error); + } + + auto* result = new EventDispatchResult{.canceled = dispatch_result == DispatchEventResult::kCanceledByEventHandler, + .propagationStopped = event->propagationStopped()}; + return NativeValueConverter<NativeTypePointer<EventDispatchResult>>::ToNativeValue(result); +} + +RegisteredEventListener* EventTarget::GetAttributeRegisteredEventListener(const AtomicString& event_type) { + EventListenerVector* listener_vector = GetEventListeners(event_type); + if (!listener_vector) + return nullptr; + + for (auto& event_listener : *listener_vector) { + auto listener = event_listener.Callback(); + if (GetExecutingContext() && listener->IsEventHandler()) + return &event_listener; + } + return nullptr; +} + +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 { + EventTarget::Trace(visitor); + data_.Trace(visitor); +} + +} // namespace webf + +// namespace webf::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..4ff238265f --- /dev/null +++ b/bridge/core/dom/events/event_target.h @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_EVENT_TARGET_H +#define BRIDGE_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/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 webf { + +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 { + WEBF_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 { + WEBF_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; +}; + +class Node; + +// 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); + explicit EventTarget(ExecutingContext* context, NativeBindingObject* native_binding_object); + + virtual Node* ToNode(); + + 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&); + + // Used for legacy "onEvent" attribute APIs. + bool SetAttributeEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& listener, + ExceptionState& exception_state); + std::shared_ptr<EventListener> GetAttributeEventListener(const AtomicString& event_type); + + EventListenerVector* GetEventListeners(const AtomicString& event_type); + + int32_t eventTargetId() const { return event_target_id_; } + + virtual bool IsWindowOrWorkerGlobalScope() const { return false; } + virtual bool IsNode() const { return false; } + bool IsEventTarget() const override; + + // Check the attribute is defined in native. + virtual bool IsAttributeDefinedInternal(const AtomicString& key) const; + + void Trace(GCVisitor* visitor) const override; + + protected: + 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(const NativeValue* native_method, int32_t argc, const NativeValue* argv) override; + NativeValue HandleDispatchEventFromDart(int32_t argc, const NativeValue* argv); + + // Subclasses should likely not override these themselves; instead, they + // should subclass EventTargetWithInlineData. + virtual EventTargetData* GetEventTargetData() = 0; + virtual EventTargetData& EnsureEventTargetData() = 0; + + private: + RegisteredEventListener* GetAttributeRegisteredEventListener(const AtomicString& event_type); + + int32_t event_target_id_; + bool FireEventListeners(Event&, EventTargetData*, EventListenerVector&, ExceptionState&); +}; + +template <> +struct DowncastTraits<EventTarget> { + static bool AllowFrom(const BindingObject& binding_object) { return binding_object.IsEventTarget(); } +}; + +// Provide EventTarget with inlined EventTargetData for improved performance. +class EventTargetWithInlineData : public EventTarget { + public: + EventTargetWithInlineData() = delete; + explicit EventTargetWithInlineData(ExecutingContext* context) : EventTarget(context){}; + explicit EventTargetWithInlineData(ExecutingContext* context, NativeBindingObject* native_binding_object) + : EventTarget(context, native_binding_object){}; + + 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 std::shared_ptr<EventListener> on##lower_name(EventTarget& eventTarget) { \ + return eventTarget.GetAttributeEventListener(event_type_names::symbol_name); \ + } \ + static void setOn##lower_name(EventTarget& eventTarget, const std::shared_ptr<EventListener>& listener, \ + ExceptionState& exception_state) { \ + eventTarget.SetAttributeEventListener(event_type_names::symbol_name, listener, exception_state); \ + } + +#define DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + std::shared_ptr<EventListener> on##lower_name() { \ + return GetDocument().GetWindowAttributeEventListener(event_type_names::symbol_name); \ + } \ + void setOn##lower_name(const std::shared_ptr<EventListener>& listener, ExceptionState& exception_state) { \ + GetDocument().SetWindowAttributeEventListener(event_type_names::symbol_name, listener, exception_state); \ + } + +#define DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + static std::shared_ptr<EventListener> on##lower_name(EventTarget& eventTarget) { \ + if (Node* node = eventTarget.ToNode()) { \ + return node->GetDocument().GetWindowAttributeEventListener(event_type_names::symbol_name); \ + } \ + return eventTarget.GetAttributeEventListener(event_type_names::symbol_name); \ + } \ + static void setOn##lower_name(EventTarget& eventTarget, const std::shared_ptr<EventListener>& listener, \ + ExceptionState& exception_state) { \ + if (Node* node = eventTarget.ToNode()) { \ + node->GetDocument().SetWindowAttributeEventListener(event_type_names::symbol_name, listener, exception_state); \ + } else { \ + eventTarget.SetAttributeEventListener(event_type_names::symbol_name, listener, exception_state); \ + } \ + } + +// +// 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 webf + +#endif // BRIDGE_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..9901e96e28 --- /dev/null +++ b/bridge/core/dom/events/event_target_impl.cc @@ -0,0 +1,5 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#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..6fca4f614a --- /dev/null +++ b/bridge/core/dom/events/event_target_impl.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ +#define BRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ + +#include "event_target.h" + +namespace webf { + +// 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 webf + +#endif // BRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ diff --git a/bridge/bindings/qjs/dom/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc similarity index 66% rename from bridge/bindings/qjs/dom/event_target_test.cc rename to bridge/core/dom/events/event_target_test.cc index 1316fa8b0b..852b3983f0 100644 --- a/bridge/bindings/qjs/dom/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -2,12 +2,15 @@ * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ - #include "event_target.h" +#include "core/dom/container_node.h" +#include "core/dom/events/event.h" +#include "event_type_names.h" #include "gtest/gtest.h" -#include "page.h" #include "webf_test_env.h" +using namespace webf; + TEST(EventTarget, addEventListener) { bool static errorCalled = false; bool static logCalled = false; @@ -19,8 +22,10 @@ TEST(EventTarget, addEventListener) { WEBF_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'));"; + 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); @@ -34,9 +39,10 @@ TEST(EventTarget, removeEventListener) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + 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'));"; + "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); @@ -54,8 +60,10 @@ TEST(EventTarget, setNoEventTargetProperties) { 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);"; + 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); } @@ -71,7 +79,7 @@ TEST(EventTarget, propertyEventHandler) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div'); " "div.onclick = function() { return 1234; };" @@ -83,44 +91,22 @@ TEST(EventTarget, propertyEventHandler) { EXPECT_EQ(logCalled, true); } -TEST(EventTarget, attributeEventHandlerShouldExit) { +TEST(EventTarget, overwritePropertyEventHandler) { bool static errorCalled = false; bool static logCalled = false; - webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "true"); - }; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->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; - webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = false; }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - WEBF_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); + 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()"; + "div.onclick = null;" + "console.log(div.onclick)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, false); } TEST(EventTarget, propertyEventOnWindow) { @@ -134,7 +120,7 @@ TEST(EventTarget, propertyEventOnWindow) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "window.onclick = function() { console.log(1234); };" "window.dispatchEvent(new Event('click'));"; @@ -154,12 +140,11 @@ TEST(EventTarget, asyncFunctionCallback) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); std::string code = R"( const img = document.createElement('img'); img.style.width = '100px'; img.style.height = '100px'; - img.src = "assets/webf.png"; document.body.appendChild(img); const img2 = img.cloneNode(false); document.body.appendChild(img2); @@ -188,29 +173,43 @@ TEST(EventTarget, asyncFunctionCallback) { TEST(EventTarget, ClassInheritEventTarget) { bool static errorCalled = false; bool static logCalled = false; - webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "ƒ () ƒ ()"); - }; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); std::string code = std::string(R"( -class Sample extends EventTarget { + class Sample extends EventTarget { constructor() { super(); } + + addEvent(event, fn) { + this.addEventListener(event, fn); + } + + removeEvent(event, fn) { + this.removeEventListener(event, fn); + } + + triggerEvent(event) { + this.dispatchEvent(event); + } } let s = new Sample(); -console.log(s.addEventListener, s.removeEventListener) +let clickCount = 0; +let f = () => clickCount++; +s.addEvent('click', f); +s.triggerEvent(new Event('click')); +s.removeEvent('click', f); +s.triggerEvent(new Event('click')); +console.assert(clickCount == 1); )"); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); } TEST(EventTarget, wontLeakWithStringProperty) { @@ -221,49 +220,6 @@ TEST(EventTarget, wontLeakWithStringProperty) { bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); } -TEST(EventTarget, dispatchEventOnGC) { - using namespace webf::binding::qjs; - - bool static errorCalled = false; - bool static logCalled = false; - webf::WebFPage::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<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; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -285,12 +241,12 @@ TEST(EventTarget, shouldKeepAtom) { }; std::string code = "addEventListener('click', () => {console.log(1)});"; bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); - JS_RunGC(bridge->getContext()->runtime()); + JS_RunGC(JS_GetRuntime(bridge->GetExecutingContext()->ctx())); std::string code2 = "addEventListener('appear', () => {console.log(2)});"; bridge->evaluateScript(code2.c_str(), code2.size(), "internal://", 0); - JS_RunGC(bridge->getContext()->runtime()); + JS_RunGC(JS_GetRuntime(bridge->GetExecutingContext()->ctx())); std::string code3 = "(function() { var eeee = new Event('appear'); dispatchEvent(eeee); } )();"; bridge->evaluateScript(code3.c_str(), code3.size(), "internal://", 0); diff --git a/bridge/bindings/qjs/dom/event_test.cc b/bridge/core/dom/events/event_test.cc similarity index 85% rename from bridge/bindings/qjs/dom/event_test.cc rename to bridge/core/dom/events/event_test.cc index e5df41d3e6..2abad684c0 100644 --- a/bridge/bindings/qjs/dom/event_test.cc +++ b/bridge/core/dom/events/event_test.cc @@ -17,7 +17,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..0201a1fd1d --- /dev/null +++ b/bridge/core/dom/events/registered_eventListener.cc @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "registered_eventListener.h" +#include "event.h" +#include "qjs_add_event_listener_options.h" + +namespace webf { + +RegisteredEventListener::RegisteredEventListener() = default; + +RegisteredEventListener::RegisteredEventListener(const std::shared_ptr<EventListener>& listener, + std::shared_ptr<AddEventListenerOptions> options) + : callback_(listener), + use_capture_(options->hasCapture() && options->capture()), + passive_(options->hasPassive() && options->passive()), + once_(options->hasOnce() && 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->hasCapture() && 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 webf diff --git a/bridge/core/dom/events/registered_eventListener.h b/bridge/core/dom/events/registered_eventListener.h new file mode 100644 index 0000000000..3f9dad7398 --- /dev/null +++ b/bridge/core/dom/events/registered_eventListener.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ +#define BRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ + +#include "event_listener.h" +#include "foundation/macros.h" + +namespace webf { + +class AddEventListenerOptions; +class EventListenerOptions; + +// RegisteredEventListener represents 'event listener' defined in the DOM +// standard. https://dom.spec.whatwg.org/#concept-event-listener +class RegisteredEventListener final { + WEBF_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 webf + +#endif // BRIDGE_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..ede130b6d6 --- /dev/null +++ b/bridge/core/dom/frame_request_callback_collection.cc @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "frame_request_callback_collection.h" + +#include <utility> +#include "bindings/qjs/cppgc/gc_visitor.h" + +namespace webf { + +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 webf 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..4bcaf238d8 --- /dev/null +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ +#define BRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ + +#include "core/executing_context.h" + +namespace webf { + +// |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 webf + +class frame_request_callback_collection {}; + +#endif // BRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ diff --git a/bridge/core/dom/global_event_handlers.d.ts b/bridge/core/dom/global_event_handlers.d.ts new file mode 100644 index 0000000000..c1456d0c47 --- /dev/null +++ b/bridge/core/dom/global_event_handlers.d.ts @@ -0,0 +1,244 @@ +type IDLEventHandler = Function; + +// @ts-ignore +@Mixin() +export interface GlobalEventHandlers { + /** + * Fires when the user aborts the download. + * @param ev The event. + */ + onabort: IDLEventHandler | null; + onanimationcancel: IDLEventHandler | null; + onanimationend: IDLEventHandler | null; + onanimationiteration: IDLEventHandler | null; + onanimationstart: IDLEventHandler | null; + /** + * Fires when the object loses the input focus. + * @param ev The focus event. + */ + onblur: IDLEventHandler | null; + oncancel: IDLEventHandler | null; + /** + * Occurs when playback is possible, but would require further buffering. + * @param ev The event. + */ + oncanplay: IDLEventHandler | null; + oncanplaythrough: IDLEventHandler | null; + /** + * Fires when the contents of the object or selection have changed. + * @param ev The event. + */ + onchange: IDLEventHandler | null; + /** + * Fires when the user clicks the left mouse button on the object + * @param ev The mouse event. + */ + onclick: IDLEventHandler | null; + onclose: IDLEventHandler | null; + /** + * Fires when the user double-clicks the object. + * @param ev The mouse event. + */ + ondblclick: IDLEventHandler | null; + /** + * Fires on the source object continuously during a drag operation. + * @param ev The event. + */ + // ondrag: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; + /** + * Fires on the source object when the user releases the mouse at the close of a drag operation. + * @param ev The event. + */ + // ondragend: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; + /** + * Fires on the target element when the user drags the object to a valid drop target. + * @param ev The drag event. + */ + // ondragenter: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; + // ondragexit: ((this: GlobalEventHandlers, ev: Event) => any) | null; + /** + * Fires on the target object when the user moves the mouse out of a valid drop target during a drag operation. + * @param ev The drag event. + */ + // ondragleave: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; + /** + * Fires on the target element continuously while the user drags the object over a valid drop target. + * @param ev The event. + */ + // ondragover: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; + /** + * Fires on the source object when the user starts to drag a text selection or selected object. + * @param ev The event. + */ + // ondragstart: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; + // ondrop: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; + /** + * Occurs when the end of playback is reached. + * @param ev The event + */ + onended: IDLEventHandler | null; + /** + * Fires when an error occurs during object loading. + * @param ev The event. + */ + onerror: IDLEventHandler | null; + /** + * Fires when the object receives focus. + * @param ev The event. + */ + onfocus: IDLEventHandler | null; + ongotpointercapture: IDLEventHandler | null; + oninput: IDLEventHandler | null; + oninvalid: IDLEventHandler | null; + /** + * Fires when the user presses a key. + * @param ev The keyboard event + */ + onkeydown: IDLEventHandler | null; + /** + * Fires when the user presses an alphanumeric key. + * @param ev The event. + */ + onkeypress: IDLEventHandler | null; + /** + * Fires when the user releases a key. + * @param ev The keyboard event + */ + onkeyup: IDLEventHandler | null; + /** + * Fires immediately after the browser loads the object. + * @param ev The event. + */ + onload: IDLEventHandler | null; + /** + * Occurs when media data is loaded at the current playback position. + * @param ev The event. + */ + onloadeddata: IDLEventHandler | null; + /** + * Occurs when the duration and dimensions of the media have been determined. + * @param ev The event. + */ + onloadedmetadata: IDLEventHandler | null; + /** + * Occurs when Internet Explorer begins looking for media data. + * @param ev The event. + */ + onloadstart: IDLEventHandler | null; + onlostpointercapture: IDLEventHandler | null; + /** + * Fires when the user clicks the object with either mouse button. + * @param ev The mouse event. + */ + onmousedown: IDLEventHandler | null; + onmouseenter: IDLEventHandler | null; + onmouseleave: IDLEventHandler | null; + /** + * Fires when the user moves the mouse over the object. + * @param ev The mouse event. + */ + onmousemove: IDLEventHandler | null; + /** + * Fires when the user moves the mouse pointer outside the boundaries of the object. + * @param ev The mouse event. + */ + onmouseout: IDLEventHandler | null; + /** + * Fires when the user moves the mouse pointer into the object. + * @param ev The mouse event. + */ + onmouseover: IDLEventHandler | null; + /** + * Fires when the user releases a mouse button while the mouse is over the object. + * @param ev The mouse event. + */ + onmouseup: IDLEventHandler | null; + /** + * Occurs when playback is paused. + * @param ev The event. + */ + onpause: IDLEventHandler | null; + /** + * Occurs when the play method is requested. + * @param ev The event. + */ + onplay: IDLEventHandler | null; + /** + * Occurs when the audio or video has started playing. + * @param ev The event. + */ + onplaying: IDLEventHandler | null; + onpointercancel: IDLEventHandler | null; + onpointerdown: IDLEventHandler | null; + onpointerenter: IDLEventHandler | null; + onpointerleave: IDLEventHandler | null; + onpointermove: IDLEventHandler | null; + onpointerout: IDLEventHandler | null; + onpointerover: IDLEventHandler | null; + onpointerup: IDLEventHandler | null; + /** + * Occurs to indicate progress while downloading media data. + * @param ev The event. + */ + // onprogress: ((this: GlobalEventHandlers, ev: ProgressEvent) => any) | null; + /** + * Occurs when the playback rate is increased or decreased. + * @param ev The event. + */ + onratechange: IDLEventHandler | null; + /** + * Fires when the user resets a form. + * @param ev The event. + */ + onreset: IDLEventHandler | null; + onresize: IDLEventHandler | null; + /** + * Fires when the user repositions the scroll box in the scroll bar on the object. + * @param ev The event. + */ + onscroll: IDLEventHandler | null; + // onsecuritypolicyviolation: ((this: GlobalEventHandlers, ev: SecurityPolicyViolationEvent) => any) | null; + /** + * Occurs when the seek operation ends. + * @param ev The event. + */ + onseeked: IDLEventHandler | null; + /** + * Occurs when the current playback position is moved. + * @param ev The event. + */ + onseeking: IDLEventHandler | null; + /** + * Fires when the current selection changes. + * @param ev The event. + */ + onselect: IDLEventHandler | null; + onselectionchange: IDLEventHandler | null; + onselectstart: IDLEventHandler | null; + /** + * Occurs when the download has stopped. + * @param ev The event. + */ + onstalled: IDLEventHandler | null; + onsubmit: IDLEventHandler | null; + /** + * Occurs if the load operation has been intentionally halted. + * @param ev The event. + */ + onsuspend: IDLEventHandler | null; + ontoggle: IDLEventHandler | null; + ontouchcancel?: IDLEventHandler | null; + ontouchend?: IDLEventHandler | null; + ontouchmove?: IDLEventHandler | null; + ontouchstart?: IDLEventHandler | null; + ontransitioncancel: IDLEventHandler | null; + ontransitionend: IDLEventHandler | null; + ontransitionrun: IDLEventHandler | null; + ontransitionstart: IDLEventHandler | null; + /** + * Occurs when playback stops because the next frame of a video resource is not available. + * @param ev The event. + */ + onwaiting: IDLEventHandler | null; + onwheel: IDLEventHandler | null; +} \ No newline at end of file diff --git a/bridge/core/dom/global_event_handlers.h b/bridge/core/dom/global_event_handlers.h new file mode 100644 index 0000000000..4f20a50441 --- /dev/null +++ b/bridge/core/dom/global_event_handlers.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_GLOBAL_EVENT_HANDLERS_H_ +#define BRIDGE_CORE_DOM_GLOBAL_EVENT_HANDLERS_H_ + +#include "core/dom/events/event_target.h" +#include "event_type_names.h" +#include "foundation/macros.h" + +namespace webf { + +class GlobalEventHandlers { + WEBF_STATIC_ONLY(GlobalEventHandlers); + + public: + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(abort, kabort); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(animationcancel, kanimationcancel); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(animationend, kanimationend); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(animationiteration, kanimationiteration); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(animationstart, kanimationstart); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(blur, kblur); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(cancel, kcancel); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(canplay, kcanplay); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(canplaythrough, kcanplaythrough); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(change, kchange); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(click, kclick); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(close, kclose); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(dblclick, kdblclick); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(ended, kended); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(error, kerror); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(focus, kfocus); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(gotpointercapture, kgotpointercapture); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(input, kinput); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(invalid, kinvalid); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(keydown, kkeydown); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(keypress, kkeypress); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(keyup, kkeyup); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(load, kload); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(loadeddata, kloadeddata); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(loadedmetadata, kloadedmetadata); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(loadstart, kloadstart); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(lostpointercapture, klostpointercapture); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(mousedown, kmousedown); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(mouseenter, kmouseenter); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(mouseleave, kmouseleave); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(mousemove, kmousemove); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(mouseout, kmouseout); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(mouseover, kmouseover); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(mouseup, kmouseup); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(pause, kpause); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(play, kplay); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(playing, kplaying); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(pointercancel, kpointercancel); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(pointerdown, kpointerdown); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(pointerenter, kpointerenter); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(pointerleave, kpointerleave); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(pointermove, kpointermove); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(pointerout, kpointerout); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(pointerover, kpointerover); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(pointerup, kpointerup); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(ratechange, kratechange); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(onreset, kratechange); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(reset, kreset); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(resize, kresize); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(scroll, kscroll); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(seeked, kseeked); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(seeking, kseeking); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(select, kselect); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(selectionchange, kselectionchange); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(selectstart, kselectstart); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(stalled, kstalled); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(submit, ksubmit); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(suspend, ksuspend); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(toggle, ktoggle); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(touchcancel, ktouchcancel); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(touchend, ktouchend); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(touchmove, ktouchmove); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(touchstart, ktouchstart); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(transitioncancel, ktransitioncancel); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(transitionend, ktransitionend); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(transitionrun, ktransitionrun); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(transitionstart, ktransitionstart); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(waiting, kwaiting); + DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(wheel, kwheel); +}; + +} // namespace webf + +#endif // BRIDGE_CORE_DOM_GLOBAL_EVENT_HANDLERS_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..ecbc5c54da --- /dev/null +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "bounding_client_rect.h" +#include "core/executing_context.h" + +namespace webf { + +BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, NativeBindingObject* native_binding_object) { + return MakeGarbageCollected<BoundingClientRect>(context, native_binding_object); +} + +BoundingClientRect::BoundingClientRect(ExecutingContext* context, NativeBindingObject* native_binding_object) + : ScriptWrappable(context->ctx()), BindingObject(context, native_binding_object) {} + +NativeValue BoundingClientRect::HandleCallFromDartSide(const NativeValue* method, + int32_t argc, + const NativeValue* argv) { + return Native_NewNull(); +} + +} // namespace webf 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..49b457e4e6 --- /dev/null +++ b/bridge/core/dom/legacy/bounding_client_rect.d.ts @@ -0,0 +1,12 @@ +interface BoundingClientRect { + readonly x: DartImpl<double>; + readonly y: DartImpl<double>; + readonly width: DartImpl<double>; + readonly height: DartImpl<double>; + readonly top: DartImpl<double>; + readonly right: DartImpl<double>; + readonly bottom: DartImpl<double>; + readonly left: DartImpl<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..0b588dccc6 --- /dev/null +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ +#define BRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ + +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/script_wrappable.h" +#include "core/binding_object.h" + +namespace webf { + +class ExecutingContext; + +class BoundingClientRect : public ScriptWrappable, public BindingObject { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = BoundingClientRect*; + BoundingClientRect() = delete; + static BoundingClientRect* Create(ExecutingContext* context, NativeBindingObject* native_binding_object); + explicit BoundingClientRect(ExecutingContext* context, NativeBindingObject* native_binding_object); + + NativeValue HandleCallFromDartSide(const NativeValue* method, int32_t argc, const NativeValue* argv) 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 webf + +#endif // BRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ diff --git a/bridge/core/dom/legacy/element_attribute_test.cc b/bridge/core/dom/legacy/element_attribute_test.cc new file mode 100644 index 0000000000..c742f5bba3 --- /dev/null +++ b/bridge/core/dom/legacy/element_attribute_test.cc @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "gtest/gtest.h" +#include "webf_test_env.h" + +using namespace webf; + +TEST(Element, overrideAttribute) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = R"( + const text = document.createElement('div'); + text.setAttribute('value', 'Hello'); + document.body.appendChild(text); + text.setAttribute('value', 'Hi'); +)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, false); +} \ No newline at end of file diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc new file mode 100644 index 0000000000..2532b934dc --- /dev/null +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "element_attributes.h" +#include "bindings/qjs/exception_state.h" +#include "built_in_string.h" +#include "core/dom/element.h" +#include "foundation/native_value_converter.h" + +namespace webf { + +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()), element_(element) {} + +AtomicString ElementAttributes::getAttribute(const AtomicString& name, ExceptionState& exception_state) { + bool numberIndex = IsNumberIndex(name.ToStringView()); + + if (numberIndex) { + return AtomicString::Empty(); + } + + AtomicString value = attributes_[name]; + + // Fallback to directly FFI access to dart. + if (value.IsEmpty()) { + NativeValue dart_result = element_->GetBindingProperty(name, exception_state); + if (dart_result.tag == NativeTag::TAG_STRING) { + return NativeValueConverter<NativeTypeString>::FromNativeValue(element_->ctx(), dart_result); + } + } + + return value; +} + +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; + } + + attributes_[name] = value; + + std::unique_ptr<NativeString> args_01 = name.ToNativeString(); + std::unique_ptr<NativeString> args_02 = value.ToNativeString(); + + GetExecutingContext()->uiCommandBuffer()->addCommand(element_->eventTargetId(), UICommand::kSetAttribute, + std::move(args_01), std::move(args_02), nullptr); + + 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); + + std::unique_ptr<NativeString> args_01 = name.ToNativeString(); + GetExecutingContext()->uiCommandBuffer()->addCommand(element_->eventTargetId(), UICommand::kRemoveAttribute, + std::move(args_01), nullptr); +} + +void ElementAttributes::CopyWith(ElementAttributes* attributes) { + for (auto& attr : attributes->attributes_) { + attributes_[attr.first] = attr.second; + } +} + +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 { + visitor->Trace(element_); +} + +} // namespace webf diff --git a/bridge/core/dom/legacy/element_attributes.d.ts b/bridge/core/dom/legacy/element_attributes.d.ts new file mode 100644 index 0000000000..ef3738f1a3 --- /dev/null +++ b/bridge/core/dom/legacy/element_attributes.d.ts @@ -0,0 +1,8 @@ +export interface ElementAttributes { + // Legacy methods: these methods are not W3C standard. + getAttribute(name: string): string; + setAttribute(name: string, value: string): void; + hasAttribute(name: string): boolean; + removeAttribute(name: string): void; + 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..fcb16f6c9c --- /dev/null +++ b/bridge/core/dom/legacy/element_attributes.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ +#define BRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ + +#include <unordered_map> +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/cppgc/member.h" +#include "bindings/qjs/script_wrappable.h" +#include "space_split_string.h" + +namespace webf { + +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); } + + explicit ElementAttributes(Element* element); + + AtomicString getAttribute(const AtomicString& name, ExceptionState& exception_state); + bool setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state); + bool hasAttribute(const AtomicString& name, ExceptionState& exception_state); + void removeAttribute(const AtomicString& name, ExceptionState& exception_state); + void CopyWith(ElementAttributes* attributes); + std::string ToString(); + + bool IsEquivalent(const ElementAttributes& other) const; + + void Trace(GCVisitor* visitor) const override; + + private: + Member<Element> element_; + std::unordered_map<AtomicString, AtomicString, AtomicString::KeyHasher> attributes_; +}; + +} // namespace webf + +#endif // BRIDGE_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..6f57494323 --- /dev/null +++ b/bridge/core/dom/legacy/space_split_string.cc @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "space_split_string.h" + +namespace webf { + +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 webf 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..b31c86939c --- /dev/null +++ b/bridge/core/dom/legacy/space_split_string.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ +#define BRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ + +#include <string> +#include <vector> + +namespace webf { + +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 webf + +#endif // BRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ diff --git a/bridge/core/dom/live_node_list_base.cc b/bridge/core/dom/live_node_list_base.cc new file mode 100644 index 0000000000..9178c9809b --- /dev/null +++ b/bridge/core/dom/live_node_list_base.cc @@ -0,0 +1,25 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2014 Samsung Electronics. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "live_node_list_base.h" diff --git a/bridge/core/dom/live_node_list_base.h b/bridge/core/dom/live_node_list_base.h new file mode 100644 index 0000000000..86dc9a6d3a --- /dev/null +++ b/bridge/core/dom/live_node_list_base.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2001 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2014 Samsung Electronics. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#ifndef BRIDGE_CORE_DOM_LIVE_NODE_LIST_BASE_H_ +#define BRIDGE_CORE_DOM_LIVE_NODE_LIST_BASE_H_ + +#include "bindings/qjs/script_wrappable.h" +#include "container_node.h" +#include "core/dom/element_traversal.h" +#include "core/html/collection_type.h" +#include "document.h" +#include "html_names.h" + +namespace webf { + +enum class NodeListSearchRoot { + kOwnerNode, + kTreeScope, +}; + +class LiveNodeListBase : public GarbageCollected<LiveNodeListBase> { + public: + explicit LiveNodeListBase(ContainerNode* owner_node, + NodeListSearchRoot search_root, + NodeListInvalidationType invalidation_type, + CollectionType collection_type) + : owner_node_(owner_node), + search_root_(static_cast<unsigned>(search_root)), + invalidation_type_(invalidation_type), + collection_type_(collection_type) { + assert(search_root_ == static_cast<unsigned>(search_root)); + assert(invalidation_type_ == static_cast<unsigned>(invalidation_type)); + assert(collection_type_ == static_cast<unsigned>(collection_type)); + } + + virtual ~LiveNodeListBase() = default; + + ContainerNode& RootNode() const; + + void DidMoveToDocument(Document& old_document, Document& new_document); + FORCE_INLINE bool IsRootedAtTreeScope() const { + return search_root_ == static_cast<unsigned>(NodeListSearchRoot::kTreeScope); + } + FORCE_INLINE NodeListInvalidationType InvalidationType() const { + return static_cast<NodeListInvalidationType>(invalidation_type_); + } + FORCE_INLINE CollectionType GetType() const { return static_cast<CollectionType>(collection_type_); } + ContainerNode& ownerNode() const { return *owner_node_; } + + virtual void InvalidateCache(Document* old_document = nullptr) const = 0; + void InvalidateCacheForAttribute(const AtomicString&) const; + + static bool ShouldInvalidateTypeOnAttributeChange(NodeListInvalidationType, const AtomicString&); + + void Trace(GCVisitor* visitor) const override { visitor->Trace(owner_node_); } + + protected: + Document& GetDocument() const { return owner_node_->GetDocument(); } + + FORCE_INLINE NodeListSearchRoot SearchRoot() const { return static_cast<NodeListSearchRoot>(search_root_); } + + template <typename MatchFunc> + static Element* TraverseMatchingElementsForwardToOffset(Element& current_element, + const ContainerNode* stay_within, + unsigned offset, + unsigned& current_offset, + MatchFunc); + template <typename MatchFunc> + static Element* TraverseMatchingElementsBackwardToOffset(Element& current_element, + const ContainerNode* stay_within, + unsigned offset, + unsigned& current_offset, + MatchFunc); + + private: + Member<ContainerNode> owner_node_; // Cannot be null. + const unsigned search_root_ : 1; + const unsigned invalidation_type_ : 4; + const unsigned collection_type_ : 5; +}; + +FORCE_INLINE bool LiveNodeListBase::ShouldInvalidateTypeOnAttributeChange(NodeListInvalidationType type, + const AtomicString& attr_name) { + switch (type) { + case kInvalidateOnClassAttrChange: + return attr_name == html_names::kClassAttr; + case kInvalidateOnNameAttrChange: + return attr_name == html_names::kNameAttr; + case kInvalidateOnIdNameAttrChange: + return attr_name == html_names::kIdAttr || attr_name == html_names::kNameAttr; + case kInvalidateOnForAttrChange: + return attr_name == html_names::kForAttr; + case kInvalidateForFormControls: + return attr_name == html_names::kNameAttr || attr_name == html_names::kIdAttr || + attr_name == html_names::kForAttr || attr_name == html_names::kFormAttr || + attr_name == html_names::kTypeAttr; + case kInvalidateOnHRefAttrChange: + return attr_name == html_names::kHrefAttr; + case kDoNotInvalidateOnAttributeChanges: + return false; + case kInvalidateOnAnyAttrChange: + return true; + } + return false; +} + +template <typename MatchFunc> +Element* LiveNodeListBase::TraverseMatchingElementsForwardToOffset(Element& current_element, + const ContainerNode* stay_within, + unsigned offset, + unsigned& current_offset, + MatchFunc is_match) { + assert(current_offset < offset); + for (Element* next = ElementTraversal::Next(current_element, stay_within, is_match); next; + next = ElementTraversal::Next(*next, stay_within, is_match)) { + if (++current_offset == offset) + return next; + } + return nullptr; +} + +template <typename MatchFunc> +Element* LiveNodeListBase::TraverseMatchingElementsBackwardToOffset(Element& current_element, + const ContainerNode* stay_within, + unsigned offset, + unsigned& current_offset, + MatchFunc is_match) { + assert(current_offset > offset); + for (Element* previous = ElementTraversal::Previous(current_element, stay_within, is_match); previous; + previous = ElementTraversal::Previous(*previous, stay_within, is_match)) { + if (--current_offset == offset) + return previous; + } + return nullptr; +} + +} // namespace webf + +#endif // BRIDGE_CORE_DOM_LIVE_NODE_LIST_BASE_H_ diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc new file mode 100644 index 0000000000..f1156b15a5 --- /dev/null +++ b/bridge/core/dom/node.cc @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF 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 "element.h" +#include "empty_node_list.h" +#include "node_data.h" +#include "node_traversal.h" +#include "qjs_node.h" +#include "text.h" + +namespace webf { + +int Node::ELEMENT_NODE = kElementNode; +int Node::ATTRIBUTE_NODE = kAttributeNode; +int Node::TEXT_NODE = kTextNode; +int Node::COMMENT_NODE = kCommentNode; +int Node::DOCUMENT_NODE = kDocumentNode; +int Node::DOCUMENT_TYPE_NODE = kDocumentTypeNode; +int Node::DOCUMENT_FRAGMENT_NODE = kDocumentFragmentNode; + +Node* Node::Create(ExecutingContext* context, ExceptionState& exception_state) { + exception_state.ThrowException(context->ctx(), ErrorType::TypeError, "Illegal constructor"); + return nullptr; +} + +Node* Node::ToNode() { + return this; +} + +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(); +} + +bool Node::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSNode::IsAttributeDefinedInternal(key) || EventTarget::IsAttributeDefinedInternal(key); +} + +Node& Node::TreeRoot() const { + const Node* node = this; + while (node->parentNode()) + 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(); + Node* new_node = Clone(GetDocument(), + deep ? (clone_shadows_flag ? CloneChildrenFlag::kCloneWithShadows : CloneChildrenFlag::kClone) + : CloneChildrenFlag::kSkip); + return new_node; +} + +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(); + + 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::IsNode() const { + return true; +} + +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 webf diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts new file mode 100644 index 0000000000..9ebd9a8cab --- /dev/null +++ b/bridge/core/dom/node.d.ts @@ -0,0 +1,86 @@ +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 { + readonly ELEMENT_NODE: StaticMember<number>; + readonly ATTRIBUTE_NODE: StaticMember<number>; + readonly TEXT_NODE: StaticMember<number>; + readonly COMMENT_NODE: StaticMember<number>; + readonly DOCUMENT_NODE: StaticMember<number>; + readonly DOCUMENT_TYPE_NODE: StaticMember<number>; + readonly DOCUMENT_FRAGMENT_NODE: StaticMember<number>; + + /** + * Returns the type of node. + */ + readonly nodeType: number; + /** + * Returns a string appropriate for the type of node. + */ + readonly nodeName: string; + + nodeValue: string | null; + + /** + * 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 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..d807270c47 --- /dev/null +++ b/bridge/core/dom/node.h @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_NODE_H +#define BRIDGE_NODE_H + +#include <set> +#include <utility> + +#include "events/event_target.h" +#include "foundation/macros.h" +#include "node_data.h" +#include "tree_scope.h" + +namespace webf { + +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, + }; + + // Constant properties. + static int ELEMENT_NODE; + static int ATTRIBUTE_NODE; + static int TEXT_NODE; + static int COMMENT_NODE; + static int DOCUMENT_NODE; + static int DOCUMENT_TYPE_NODE; + static int DOCUMENT_FRAGMENT_NODE; + + using ImplType = Node*; + static Node* Create(ExecutingContext* context, ExceptionState& exception_state); + + Node* ToNode() override; + + // 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 IsNode() const override; + 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(); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + 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, + kIsWidgetElement = 1 << 25, + + 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), + kCreateWidgetElement = kDefaultNodeFlags | kIsContainerFlag | static_cast<NodeFlags>(DOMNodeType::kElement) | + static_cast<NodeFlags>(ElementNamespaceType::kHTML) | kIsWidgetElement, + 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_; +}; + +template <> +struct DowncastTraits<Node> { + static bool AllowFrom(const EventTarget& event_target) { return event_target.IsNode(); } +}; + +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 webf + +#endif // BRIDGE_NODE_H diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc new file mode 100644 index 0000000000..cb5b3b5261 --- /dev/null +++ b/bridge/core/dom/node_data.cc @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF 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 webf { + +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 webf diff --git a/bridge/core/dom/node_data.h b/bridge/core/dom/node_data.h new file mode 100644 index 0000000000..c127ec758c --- /dev/null +++ b/bridge/core/dom/node_data.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_NODE_DATA_H_ +#define BRIDGE_CORE_DOM_NODE_DATA_H_ + +#include <cinttypes> +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "bindings/qjs/cppgc/gc_visitor.h" + +namespace webf { + +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 webf + +#endif // BRIDGE_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..b7c3c0263c --- /dev/null +++ b/bridge/core/dom/node_list.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2013, Opera Software ASA. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Opera Software ASA nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_NODE_LIST_H_ +#define BRIDGE_CORE_DOM_NODE_LIST_H_ + +#include "bindings/qjs/script_wrappable.h" + +namespace webf { + +class Node; +class ExceptionState; +class AtomicString; + +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; + + virtual bool NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state) = 0; + virtual void NamedPropertyEnumerator(std::vector<AtomicString>& names, ExceptionState& exception_state) = 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 webf + +#endif // BRIDGE_CORE_DOM_NODE_LIST_H_ diff --git a/bridge/bindings/qjs/dom/node_test.cc b/bridge/core/dom/node_test.cc similarity index 50% rename from bridge/bindings/qjs/dom/node_test.cc rename to bridge/core/dom/node_test.cc index bda58b29be..46551b0a93 100644 --- a/bridge/bindings/qjs/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -3,11 +3,11 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#include "event_target.h" #include "gtest/gtest.h" -#include "page.h" #include "webf_test_env.h" +using namespace webf; + TEST(Node, appendChild) { bool static errorCalled = false; bool static logCalled = false; @@ -16,11 +16,33 @@ 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); + EXPECT_EQ(logCalled, true); +} + +TEST(Node, nodeName) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "DIV #text #document-fragment #comment #document"); + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let div = document.createElement('div');" + "let text = document.createTextNode('helloworld');" + "let fragment = document.createDocumentFragment();" + "let comment = document.createComment();" + "console.log(div.nodeName, text.nodeName, fragment.nodeName, comment.nodeName, document.nodeName)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -35,7 +57,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');" @@ -52,6 +75,21 @@ TEST(Node, childNodes) { EXPECT_EQ(logCalled, true); } +TEST(Node, textNodeHaveEmptyChildNodes) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + auto 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; @@ -60,7 +98,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');" @@ -82,7 +120,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';" @@ -101,7 +139,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);" @@ -123,7 +161,9 @@ TEST(Node, replaceBody) { WEBF_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); @@ -132,20 +172,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; @@ -158,7 +199,7 @@ console.log(div.style.width == div2.style.height, div.getAttribute('id') == '123 WEBF_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); @@ -167,32 +208,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' ); )"; @@ -206,9 +248,77 @@ console.log( WEBF_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); EXPECT_EQ(logCalled, true); } + +TEST(Node, isConnected) { + std::string code = R"( +const el = document.createElement('div'); +console.assert(el.isConnected == false); +document.body.appendChild(el); +console.assert(el.isConnected == true); + +const child_0 = document.createTextNode('first child'); +el.appendChild(child_0); +console.assert(el.firstChild === child_0); +console.assert(el.lastChild === child_0); + +const child_1 = document.createTextNode('second child'); +el.appendChild(child_1); +console.assert(child_1.previousSibling === child_0); +console.assert(child_0.nextSibling === child_1); + +el.removeChild(child_0); +const child_2 = document.createTextNode('third child'); + +el.insertBefore(child_2, child_1); +const child_3 = document.createTextNode('fourth child'); +el.replaceChild(child_3, child_1); +)"; + + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::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) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, false); +} + +TEST(Node, isConnectedWhenRemove) { + std::string code = R"( +const el = document.createElement('div'); +document.body.appendChild(el); +console.assert(el.isConnected); +el.remove(); +console.assert(el.isConnected == false); +)"; + + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::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) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, false); +} \ No newline at end of file diff --git a/bridge/core/dom/node_traversal.cc b/bridge/core/dom/node_traversal.cc new file mode 100644 index 0000000000..e7490855f1 --- /dev/null +++ b/bridge/core/dom/node_traversal.cc @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "node_traversal.h" + +namespace webf { + +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 webf diff --git a/bridge/core/dom/node_traversal.h b/bridge/core/dom/node_traversal.h new file mode 100644 index 0000000000..451088a575 --- /dev/null +++ b/bridge/core/dom/node_traversal.h @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2013, Opera Software ASA. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Opera Software ASA nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ +#define BRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ + +#include "container_node.h" +#include "foundation/macros.h" +#include "node.h" +#include "traversal_range.h" + +namespace webf { + +class NodeTraversal { + WEBF_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 webf + +#endif // BRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ diff --git a/bridge/core/dom/parent_node.cc b/bridge/core/dom/parent_node.cc new file mode 100644 index 0000000000..ec76cbb6be --- /dev/null +++ b/bridge/core/dom/parent_node.cc @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "parent_node.h" +#include "element_traversal.h" + +namespace webf { + +Element* ParentNode::firstElementChild(ContainerNode& node) { + return ElementTraversal::FirstChild(node); +} + +Element* ParentNode::lastElementChild(ContainerNode& node) { + return ElementTraversal ::LastChild(node); +} + +std::vector<Element*> ParentNode::children(ContainerNode& node) { + return node.Children(); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/dom/parent_node.d.ts b/bridge/core/dom/parent_node.d.ts new file mode 100644 index 0000000000..662b25f0c4 --- /dev/null +++ b/bridge/core/dom/parent_node.d.ts @@ -0,0 +1,11 @@ +// @ts-ignore +import {HTMLAllCollection} from "../html/html_all_collection"; +import {Element} from "./element"; + +// @ts-ignore +@Mixin() +export interface ParentNode { + readonly firstElementChild: Element | null; + readonly lastElementChild: Element | null; + readonly children: Element[]; +} \ No newline at end of file diff --git a/bridge/core/dom/parent_node.h b/bridge/core/dom/parent_node.h new file mode 100644 index 0000000000..4cedf9418a --- /dev/null +++ b/bridge/core/dom/parent_node.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_BOM_PARENT_NODE_H_ +#define BRIDGE_BINDINGS_QJS_BOM_PARENT_NODE_H_ + +#include <vector> +#include "foundation/macros.h" + +namespace webf { + +class Element; +class ContainerNode; + +class ParentNode { + WEBF_STATIC_ONLY(ParentNode); + + public: + static Element* firstElementChild(ContainerNode& node); + static Element* lastElementChild(ContainerNode& node); + static std::vector<Element*> children(ContainerNode& node); +}; + +} // namespace webf + +#endif diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc new file mode 100644 index 0000000000..c67ce181b1 --- /dev/null +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "scripted_animation_controller.h" +#include "frame_request_callback_collection.h" + +namespace webf { + +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->IsContextValid()) + 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 webf diff --git a/bridge/core/dom/scripted_animation_controller.h b/bridge/core/dom/scripted_animation_controller.h new file mode 100644 index 0000000000..0fa77e525e --- /dev/null +++ b/bridge/core/dom/scripted_animation_controller.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ +#define BRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ + +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "frame_request_callback_collection.h" + +namespace webf { + +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 webf + +#endif // BRIDGE_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..c5446e94d8 --- /dev/null +++ b/bridge/core/dom/text.cc @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "text.h" +#include "document.h" + +namespace webf { + +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(), 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 { + Node* copy = Create(document, data()); + std::unique_ptr<NativeString> args_01 = stringToNativeString(std::to_string(copy->eventTargetId())); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCloneNode, std::move(args_01), + nullptr); + return copy; +} + +} // namespace webf 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..a4337d7352 --- /dev/null +++ b/bridge/core/dom/text.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_TEXT_H_ +#define BRIDGE_CORE_DOM_TEXT_H_ + +#include "character_data.h" + +namespace webf { + +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(); }; + static bool AllowFrom(const CharacterData& character_data) { return character_data.IsTextNode(); } +}; + +} // namespace webf + +#endif // BRIDGE_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..a7b04a93cb --- /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 webf { + +class Node; + +template <class Iterator> +class TraversalRange { + WEBF_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 { + WEBF_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 webf + +#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..ba21eaf88b --- /dev/null +++ b/bridge/core/dom/tree_scope.cc @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "tree_scope.h" +#include "document.h" + +namespace webf { + +TreeScope::TreeScope(Document& document) : root_node_(&document), document_(&document) { + root_node_->SetTreeScope(this); +} + +} // namespace webf diff --git a/bridge/core/dom/tree_scope.h b/bridge/core/dom/tree_scope.h new file mode 100644 index 0000000000..768b0568d0 --- /dev/null +++ b/bridge/core/dom/tree_scope.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_DOM_TREE_SCOPE_H_ +#define BRIDGE_CORE_DOM_TREE_SCOPE_H_ + +#include <cassert> + +namespace webf { + +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 webf + +#endif // BRIDGE_CORE_DOM_TREE_SCOPE_H_ diff --git a/bridge/core/events/animation_event.cc b/bridge/core/events/animation_event.cc new file mode 100644 index 0000000000..9eb3da2d39 --- /dev/null +++ b/bridge/core/events/animation_event.cc @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "animation_event.h" +#include "event_type_names.h" + +namespace webf { + +AnimationEvent* AnimationEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { + return MakeGarbageCollected<AnimationEvent>(context, type, exception_state); +} + +AnimationEvent* AnimationEvent::Create(ExecutingContext* context, + const AtomicString& type, + const AtomicString& animation_name, + const AtomicString& pseudo_element, + double elapsed_time, + ExceptionState& exception_state) { + return MakeGarbageCollected<AnimationEvent>(context, type, animation_name, pseudo_element, elapsed_time, + exception_state); +} +AnimationEvent* AnimationEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<AnimationEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<AnimationEvent>(context, type, initializer, exception_state); +} + +AnimationEvent::AnimationEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : Event(context, type) {} + +AnimationEvent::AnimationEvent(ExecutingContext* context, + const AtomicString& type, + const AtomicString& animation_name, + const AtomicString& pseudo_element, + double elapsed_time, + ExceptionState& exception_state) + : Event(context, type), + animation_name_(animation_name), + pseudo_element_(pseudo_element), + elapsed_time_(elapsed_time) {} + +AnimationEvent::AnimationEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<AnimationEventInit>& initializer, + ExceptionState& exception_state) + : Event(context, type), + animation_name_(initializer->hasAnimationName() ? initializer->animationName() : AtomicString::Empty()), + pseudo_element_(initializer->hasPseudoElement() ? initializer->pseudoElement() : AtomicString::Empty()), + elapsed_time_(initializer->hasElapsedTime() ? initializer->elapsedTime() : 0) {} + +const AtomicString& AnimationEvent::animationName() const { + return animation_name_; +} + +bool AnimationEvent::IsAnimationEvent() const { + return true; +} + +double AnimationEvent::elapsedTime() const { + return elapsed_time_; +} + +const AtomicString& AnimationEvent::pseudoElement() const { + return pseudo_element_; +} + +} // namespace webf diff --git a/bridge/core/events/animation_event.d.ts b/bridge/core/events/animation_event.d.ts new file mode 100644 index 0000000000..77dd03556b --- /dev/null +++ b/bridge/core/events/animation_event.d.ts @@ -0,0 +1,10 @@ +import {Event} from "../dom/events/event"; +import {AnimationEventInit} from "./animation_event_init"; + +/** Events providing information related to animations. */ +interface AnimationEvent extends Event { + readonly animationName: string; + readonly elapsedTime: number; + readonly pseudoElement: string; + new(type: string, init?: AnimationEventInit): AnimationEvent; +} \ No newline at end of file diff --git a/bridge/core/events/animation_event.h b/bridge/core/events/animation_event.h new file mode 100644 index 0000000000..46cd8943d6 --- /dev/null +++ b/bridge/core/events/animation_event.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_EVENTS_ANIMATION_EVENT_H_ +#define BRIDGE_CORE_EVENTS_ANIMATION_EVENT_H_ + +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/source_location.h" +#include "core/dom/events/event.h" +#include "qjs_animation_event_init.h" + +namespace webf { + +class AnimationEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = AnimationEvent*; + static AnimationEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + static AnimationEvent* Create(ExecutingContext* context, + const AtomicString& type, + const AtomicString& animation_name, + const AtomicString& pseudo_element, + double elapsed_time, + ExceptionState& exception_state); + static AnimationEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<AnimationEventInit>& initializer, + ExceptionState& exception_state); + + explicit AnimationEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + explicit AnimationEvent(ExecutingContext* context, + const AtomicString& type, + const AtomicString& animation_name, + const AtomicString& pseudo_element, + double elapsed_time, + ExceptionState& exception_state); + explicit AnimationEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<AnimationEventInit>& initializer, + ExceptionState& exception_state); + + const AtomicString& animationName() const; + double elapsedTime() const; + const AtomicString& pseudoElement() const; + + bool IsAnimationEvent() const override; + + private: + AtomicString animation_name_; + AtomicString pseudo_element_; + double elapsed_time_; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_EVENTS_ANIMATION_EVENT_H_ diff --git a/bridge/core/events/animation_event_init.d.ts b/bridge/core/events/animation_event_init.d.ts new file mode 100644 index 0000000000..7d76dbe7d4 --- /dev/null +++ b/bridge/core/events/animation_event_init.d.ts @@ -0,0 +1,9 @@ +import { EventInit } from "../dom/events/event_init"; + +// @ts-ignore +@Dictionary() +export interface AnimationEventInit extends EventInit { + animationName?: string; + elapsedTime?: number; + pseudoElement?: string; +} diff --git a/bridge/core/events/close_event.cc b/bridge/core/events/close_event.cc new file mode 100644 index 0000000000..addd06be00 --- /dev/null +++ b/bridge/core/events/close_event.cc @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "close_event.h" +#include "qjs_close_event.h" + +namespace webf { + +CloseEvent* CloseEvent::Create(ExecutingContext* context, + const AtomicString& type, + int32_t code, + const AtomicString& reason, + bool was_clean, + ExceptionState& exception_state) { + return MakeGarbageCollected<CloseEvent>(context, type, code, reason, was_clean, exception_state); +} + +CloseEvent* CloseEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { + return MakeGarbageCollected<CloseEvent>(context, type, exception_state); +} + +CloseEvent* CloseEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<CloseEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<CloseEvent>(context, type, initializer, exception_state); +} + +CloseEvent::CloseEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : Event(context, type) {} + +CloseEvent::CloseEvent(ExecutingContext* context, + const AtomicString& type, + int32_t code, + const AtomicString& reason, + bool was_clean, + ExceptionState& exception_state) + : Event(context, type), code_(code), reason_(reason), was_clean_(was_clean) {} + +CloseEvent::CloseEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<CloseEventInit>& initializer, + ExceptionState& exception_state) + : Event(context, type), + code_(initializer->hasCode() ? initializer->code() : 0), + reason_(initializer->hasReason() ? initializer->reason() : AtomicString::Empty()), + was_clean_(initializer->hasWasClean() && initializer->wasClean()) {} + +CloseEvent::CloseEvent(ExecutingContext* context, const AtomicString& type, NativeCloseEvent* native_close_event) + : Event(context, type, &native_close_event->native_event), + code_(native_close_event->code), +#if ANDROID_32_BIT + reason_(AtomicString(context->ctx(), reinterpret_cast<NativeString*>(native_close_event->reason))), +#else + reason_(AtomicString(context->ctx(), native_close_event->reason)), +#endif + was_clean_(native_close_event->wasClean) { +} + +bool CloseEvent::IsCloseEvent() const { + return true; +} + +int64_t CloseEvent::code() const { + return code_; +} + +const AtomicString& CloseEvent::reason() const { + return reason_; +} + +bool CloseEvent::wasClean() const { + return was_clean_; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/close_event.d.ts b/bridge/core/events/close_event.d.ts new file mode 100644 index 0000000000..97935bd584 --- /dev/null +++ b/bridge/core/events/close_event.d.ts @@ -0,0 +1,9 @@ +import {Event} from "../dom/events/event"; +import {CloseEventInit} from "./close_event_init"; + +interface CloseEvent extends Event { + readonly code: int64; + readonly reason: string; + readonly wasClean: boolean; + new(type: string, init?: CloseEventInit): CloseEvent; +} diff --git a/bridge/core/events/close_event.h b/bridge/core/events/close_event.h new file mode 100644 index 0000000000..fb19fffcaa --- /dev/null +++ b/bridge/core/events/close_event.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CLOSE_EVENT_H +#define BRIDGE_CLOSE_EVENT_H + +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/source_location.h" +#include "core/dom/events/event.h" +#include "qjs_close_event_init.h" + +namespace webf { + +struct NativeCloseEvent; + +class CloseEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = CloseEvent*; + static CloseEvent* Create(ExecutingContext* context, + const AtomicString& type, + int32_t code, + const AtomicString& reason, + bool was_clean, + ExceptionState& exception_state); + + static CloseEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + static CloseEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<CloseEventInit>& initializer, + ExceptionState& exception_state); + + explicit CloseEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + explicit CloseEvent(ExecutingContext* context, + const AtomicString& type, + int32_t code, + const AtomicString& reason, + bool was_clean, + ExceptionState& exception_state); + explicit CloseEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<CloseEventInit>& initializer, + ExceptionState& exception_state); + explicit CloseEvent(ExecutingContext* context, const AtomicString& type, NativeCloseEvent* raw_event); + + bool IsCloseEvent() const override; + + int64_t code() const; + const AtomicString& reason() const; + bool wasClean() const; + + private: + int64_t code_; + AtomicString reason_; + bool was_clean_; +}; + +} // namespace webf + +#endif // BRIDGE_CLOSE_EVENT_H diff --git a/bridge/core/events/close_event_init.d.ts b/bridge/core/events/close_event_init.d.ts new file mode 100644 index 0000000000..8f1aa052ae --- /dev/null +++ b/bridge/core/events/close_event_init.d.ts @@ -0,0 +1,9 @@ +import { EventInit } from "../dom/events/event_init"; + +// @ts-ignore +@Dictionary() +export interface CloseEventInit extends EventInit { + code?: int64; + reason?: string; + wasClean?: boolean; +} diff --git a/bridge/core/events/dart_created_events.json5 b/bridge/core/events/dart_created_events.json5 new file mode 100644 index 0000000000..23f46522d7 --- /dev/null +++ b/bridge/core/events/dart_created_events.json5 @@ -0,0 +1,108 @@ +{ + "metadata": { + "templates": [ + { + "template": "event_factory", + "filename": "event_factory" + }, + { + "template": "event_type_helper", + "filename": "event_type_helper" + } + ] + }, + "data": [ +// { +// "class": "AnimationEvent", +// "types": [ +// "animationstart", +// "animationend", +// "animationcancel", +// "animationiteration" +// ] +// }, + "close", +// "error", +// "focus", + { + "class": "PopStateEvent", + "types": [ + "popstate" + ] + }, + { + "class": "GestureEvent", + "types": [ + "gesturestart", + "gesturechange", + "gestureend" + ] + }, + "input", + { + "class": "IntersectionChangeEvent", + "types": [ + "intersectionchange" + ] + }, +// { +// "class": "KeyboardEvent", +// "types": [ +// "keydown", +// "keypress", +// "keystatuseschange", +// "keyup" +// ] +// }, + "message", +// { +// "class": "PointerEvent", +// "types": [ +// "pointercancel", +// "pointerdown", +// "pointerenter", +// "pointerleave", +// "pointerlockchange", +// "pointerlockerror", +// "pointermove", +// "pointerout", +// "pointerover", +// "pointerup" +// ] +// }, + { + "class": "TouchEvent", + "types": [ + "touchstart", + "touchend", + "touchcancel", + "touchmove" + ] + }, + { + "class": "MouseEvent", + "types": [ + "click", + "dbclick", + "longpress" +// "mousedown", +// "mouseenter", +// "mouseleave", +// "mousemove", +// "mouseout", +// "mouseover", +// "mouseup", +// "mousewheel" + ] + }, +// { +// "class": "TransitionEvent", +// "types": [ +// "transitioncancel", +// "transitionend", +// "transitionrun", +// "transitionstart" +// ] +// } + ] +} diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc new file mode 100644 index 0000000000..6791667f65 --- /dev/null +++ b/bridge/core/events/error_event.cc @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "error_event.h" +#include "event_type_names.h" + +namespace webf { + +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, event_type_names::kerror), + 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, event_type_names::kerror), message_(message), source_location_(std::move(location)) {} + +ErrorEvent::ErrorEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : Event(context, event_type_names::kerror), + 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, event_type_names::kerror), + message_(initializer->hasMessage() ? type.ToStdString() : ""), + error_(initializer->hasError() ? initializer->error() : ScriptValue::Empty(ctx())), + source_location_( + std::make_unique<SourceLocation>(initializer->hasFilename() ? initializer->filename().ToStdString() : "", + initializer->lineno(), + initializer->colno())) {} + +bool ErrorEvent::IsErrorEvent() const { + return true; +} + +} // namespace webf 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..357c917393 --- /dev/null +++ b/bridge/core/events/error_event.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ +#define BRIDGE_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 webf { + +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(); } + + bool IsErrorEvent() const override; + + 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 webf + +#endif // BRIDGE_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..57ec82797c --- /dev/null +++ b/bridge/core/events/event_type_names.json5 @@ -0,0 +1,228 @@ +{ + "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", + "dbclick", + "longpress", + "close", + "closing", + "complete", + "gotpointercapture", + "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", + "gesturestart", + "gesturechange", + "gestureend", + "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", + "intersectionchange" + ] +} diff --git a/bridge/core/events/focus_event.cc b/bridge/core/events/focus_event.cc new file mode 100644 index 0000000000..9adadab82f --- /dev/null +++ b/bridge/core/events/focus_event.cc @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "focus_event.h" +#include "core/dom/events/event_target.h" +#include "core/frame/window.h" +#include "qjs_focus_event.h" + +namespace webf { + +FocusEvent* FocusEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { + return MakeGarbageCollected<FocusEvent>(context, type, exception_state); +} + +FocusEvent* FocusEvent::Create(ExecutingContext* context, + const AtomicString& type, + double detail, + Window* view, + double which, + EventTarget* relatedTarget, + ExceptionState& exception_state) { + return MakeGarbageCollected<FocusEvent>(context, type, detail, view, which, relatedTarget, exception_state); +} + +FocusEvent* FocusEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<FocusEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<FocusEvent>(context, type, initializer, exception_state); +} + +FocusEvent::FocusEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : UIEvent(context, type, exception_state) {} + +FocusEvent::FocusEvent(ExecutingContext* context, + const AtomicString& type, + double detail, + Window* view, + double which, + EventTarget* relatedTarget, + ExceptionState& exception_state) + : UIEvent(context, type, detail, view, which, exception_state), related_target_(relatedTarget) {} + +FocusEvent::FocusEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<FocusEventInit>& initializer, + ExceptionState& exception_state) + : UIEvent(context, type, initializer, exception_state), + related_target_(initializer->hasRelatedTarget() ? initializer->relatedTarget() : nullptr) {} + +FocusEvent::FocusEvent(ExecutingContext* context, const AtomicString& type, NativeFocusEvent* native_focus_event) + : UIEvent(context, type, &native_focus_event->native_event), +#if ANDROID_32_BIT + related_target_(DynamicTo<EventTarget>( + BindingObject::From(reinterpret_cast<NativeBindingObject*>(native_focus_event->relatedTarget)))) { +} +#else + related_target_(DynamicTo<EventTarget>( + BindingObject::From(static_cast<NativeBindingObject*>(native_focus_event->relatedTarget)))) { +} +#endif + +EventTarget* FocusEvent::relatedTarget() const { + return related_target_; +} + +bool FocusEvent::IsFocusEvent() const { + return true; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/focus_event.d.ts b/bridge/core/events/focus_event.d.ts new file mode 100644 index 0000000000..019352394d --- /dev/null +++ b/bridge/core/events/focus_event.d.ts @@ -0,0 +1,9 @@ +import {UIEvent} from "./ui_event"; +import {EventTarget} from "../dom/events/event_target"; +import {FocusEventInit} from "./focus_event_init"; + +/** Focus-related events like focus, blur, focusin, or focusout. */ +interface FocusEvent extends UIEvent { + readonly relatedTarget: EventTarget | null; + new(type: string, init?: FocusEventInit): FocusEvent; +} \ No newline at end of file diff --git a/bridge/core/events/focus_event.h b/bridge/core/events/focus_event.h new file mode 100644 index 0000000000..c9fa1cc5f3 --- /dev/null +++ b/bridge/core/events/focus_event.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_EVENTS_FOCUS_EVENT_H_ +#define BRIDGE_CORE_EVENTS_FOCUS_EVENT_H_ + +#include "bindings/qjs/cppgc/member.h" +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/source_location.h" +#include "core/dom/events/event.h" +#include "qjs_focus_event_init.h" +#include "ui_event.h" + +namespace webf { + +struct NativeFocusEvent; + +class FocusEvent : public UIEvent { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = FocusEvent*; + + static FocusEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + static FocusEvent* Create(ExecutingContext* context, + const AtomicString& type, + double detail, + Window* view, + double which, + EventTarget* relatedTarget, + ExceptionState& exception_state); + static FocusEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<FocusEventInit>& initializer, + ExceptionState& exception_state); + + explicit FocusEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + explicit FocusEvent(ExecutingContext* context, + const AtomicString& type, + double detail, + Window* view, + double which, + EventTarget* relatedTarget, + ExceptionState& exception_state); + + explicit FocusEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<FocusEventInit>& initializer, + ExceptionState& exception_state); + + explicit FocusEvent(ExecutingContext* context, const AtomicString& type, NativeFocusEvent* native_focus_event); + + EventTarget* relatedTarget() const; + + bool IsFocusEvent() const override; + + private: + Member<EventTarget> related_target_; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_EVENTS_FOCUS_EVENT_H_ diff --git a/bridge/core/events/focus_event_init.d.ts b/bridge/core/events/focus_event_init.d.ts new file mode 100644 index 0000000000..ea3284e9ab --- /dev/null +++ b/bridge/core/events/focus_event_init.d.ts @@ -0,0 +1,8 @@ +import { EventTarget } from "../dom/events/event_target"; +import {UIEventInit} from "./ui_event_init"; + +// @ts-ignore +@Dictionary() +export interface FocusEventInit extends UIEventInit { + relatedTarget?: EventTarget | null; +} diff --git a/bridge/core/events/gesture_event.cc b/bridge/core/events/gesture_event.cc new file mode 100644 index 0000000000..3f82a2a310 --- /dev/null +++ b/bridge/core/events/gesture_event.cc @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "gesture_event.h" +#include "qjs_gesture_event.h" + +namespace webf { + +GestureEvent* GestureEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { + return MakeGarbageCollected<GestureEvent>(context, type, exception_state); +} + +GestureEvent* GestureEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<GestureEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<GestureEvent>(context, type, initializer, exception_state); +} + +GestureEvent::GestureEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : Event(context, type) {} + +GestureEvent::GestureEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<GestureEventInit>& initializer, + ExceptionState& exception_state) + : Event(context, type), + state_(initializer->hasState() ? initializer->state() : AtomicString::Empty()), + direction_(initializer->hasDirection() ? initializer->direction() : AtomicString::Empty()), + deltaX_(initializer->hasDeltaX() ? initializer->deltaX() : 0.0), + deltaY_(initializer->hasDeltaY() ? initializer->deltaY() : 0.0), + scale_(initializer->hasScale() ? initializer->scale() : 0.0), + rotation_(initializer->hasRotation() ? initializer->rotation() : 0.0) {} + +GestureEvent::GestureEvent(ExecutingContext* context, + const AtomicString& type, + NativeGestureEvent* native_gesture_event) + : Event(context, type, &native_gesture_event->native_event), +#if ANDROID_32_BIT + state_(AtomicString(ctx(), reinterpret_cast<NativeString*>(native_gesture_event->state))), + direction_(AtomicString(ctx(), reinterpret_cast<NativeString*>(native_gesture_event->direction))), +#else + state_(AtomicString(ctx(), native_gesture_event->state)), + direction_(AtomicString(ctx(), native_gesture_event->direction)), +#endif + deltaX_(native_gesture_event->deltaX), + deltaY_(native_gesture_event->deltaY), + velocityX_(native_gesture_event->velocityX), + velocityY_(native_gesture_event->velocityY), + scale_(native_gesture_event->scale), + rotation_(native_gesture_event->rotation) { +} + +bool GestureEvent::IsGestureEvent() const { + return true; +} + +const AtomicString& GestureEvent::state() const { + return state_; +} + +const AtomicString& GestureEvent::direction() const { + return direction_; +} + +double GestureEvent::deltaX() const { + return deltaX_; +} + +double GestureEvent::deltaY() const { + return deltaY_; +} + +double GestureEvent::velocityX() const { + return velocityX_; +} + +double GestureEvent::velocityY() const { + return velocityY_; +} + +double GestureEvent::scale() const { + return scale_; +} + +double GestureEvent::rotation() const { + return rotation_; +} + +} // namespace webf diff --git a/bridge/bindings/qjs/dom/events/gesture_event.d.ts b/bridge/core/events/gesture_event.d.ts similarity index 62% rename from bridge/bindings/qjs/dom/events/gesture_event.d.ts rename to bridge/core/events/gesture_event.d.ts index be63cc1e58..de8876ef3e 100644 --- a/bridge/bindings/qjs/dom/events/gesture_event.d.ts +++ b/bridge/core/events/gesture_event.d.ts @@ -1,4 +1,5 @@ -interface Event {} +import {Event} from "../dom/events/event"; +import {GestureEventInit} from "./gesture_event_init"; interface GestureEvent extends Event { readonly state: string; @@ -9,4 +10,5 @@ interface GestureEvent extends Event { readonly velocityY: number; readonly scale: number; readonly rotation: number; + new(type: string, init?: GestureEventInit): GestureEvent; } diff --git a/bridge/core/events/gesture_event.h b/bridge/core/events/gesture_event.h new file mode 100644 index 0000000000..6c6a6be69c --- /dev/null +++ b/bridge/core/events/gesture_event.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_EVENTS_GESTURE_EVENT_H_ +#define BRIDGE_CORE_EVENTS_GESTURE_EVENT_H_ + +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/source_location.h" +#include "core/dom/events/event.h" +#include "qjs_gesture_event_init.h" + +namespace webf { + +struct NativeGestureEvent; + +class GestureEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = GestureEvent*; + + static GestureEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + static GestureEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<GestureEventInit>& initializer, + ExceptionState& exception_state); + + explicit GestureEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + explicit GestureEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<GestureEventInit>& initializer, + ExceptionState& exception_state); + + explicit GestureEvent(ExecutingContext* context, const AtomicString& type, NativeGestureEvent* native_gesture_event); + + const AtomicString& state() const; + const AtomicString& direction() const; + double deltaX() const; + double deltaY() const; + double velocityX() const; + double velocityY() const; + double scale() const; + double rotation() const; + + bool IsGestureEvent() const override; + + private: + AtomicString state_; + AtomicString direction_; + double deltaX_; + double deltaY_; + double velocityX_; + double velocityY_; + double scale_; + double rotation_; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_EVENTS_GESTURE_EVENT_H_ diff --git a/bridge/core/events/gesture_event_init.d.ts b/bridge/core/events/gesture_event_init.d.ts new file mode 100644 index 0000000000..949b1aecad --- /dev/null +++ b/bridge/core/events/gesture_event_init.d.ts @@ -0,0 +1,14 @@ +import { EventInit } from "../dom/events/event_init"; + +// @ts-ignore +@Dictionary() +export interface GestureEventInit extends EventInit { + state?: string; + direction?: string; + deltaX?: number; + deltaY?: number; + velocityX?: number; + velocityY?: number; + scale?: number; + rotation?: number; +} diff --git a/bridge/core/events/input_event.cc b/bridge/core/events/input_event.cc new file mode 100644 index 0000000000..1fe4bc3f90 --- /dev/null +++ b/bridge/core/events/input_event.cc @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "input_event.h" +#include "qjs_input_event.h" + +namespace webf { + +InputEvent* InputEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { + return MakeGarbageCollected<InputEvent>(context, type, exception_state); +} + +InputEvent* InputEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<InputEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<InputEvent>(context, type, initializer, exception_state); +} + +InputEvent::InputEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : UIEvent(context, type, exception_state) {} + +InputEvent::InputEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<InputEventInit>& initializer, + ExceptionState& exception_state) + : UIEvent(context, type, initializer, exception_state), + input_type_(initializer->hasInputType() ? initializer->inputType() : AtomicString::Empty()), + data_(initializer->hasData() ? initializer->data() : AtomicString::Empty()) {} + +InputEvent::InputEvent(ExecutingContext* context, const AtomicString& type, NativeInputEvent* native_input_event) + : UIEvent(context, type, &native_input_event->native_event), +#if ANDROID_32_BIT + input_type_(AtomicString(ctx(), reinterpret_cast<NativeString*>(native_input_event->inputType))), + data_(AtomicString(ctx(), reinterpret_cast<NativeString*>(native_input_event->data))) +#else + input_type_(AtomicString(ctx(), native_input_event->inputType)), + data_(AtomicString(ctx(), native_input_event->data)) +#endif +{ +} + +const AtomicString& InputEvent::inputType() const { + return input_type_; +} + +const AtomicString& InputEvent::data() const { + return data_; +} + +bool InputEvent::IsInputEvent() const { + return true; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/input_event.d.ts b/bridge/core/events/input_event.d.ts new file mode 100644 index 0000000000..e7f45ff09c --- /dev/null +++ b/bridge/core/events/input_event.d.ts @@ -0,0 +1,8 @@ +import {UIEvent} from "./ui_event"; +import {InputEventInit} from "./input_event_init"; + +interface InputEvent extends UIEvent { + readonly inputType: string; + readonly data: string; + new(type: string, init?: InputEventInit): InputEvent; +} diff --git a/bridge/core/events/input_event.h b/bridge/core/events/input_event.h new file mode 100644 index 0000000000..a995225b60 --- /dev/null +++ b/bridge/core/events/input_event.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_INPUT_EVENT_H +#define BRIDGE_INPUT_EVENT_H + +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/source_location.h" +#include "qjs_input_event_init.h" +#include "ui_event.h" + +namespace webf { + +struct NativeInputEvent; + +class InputEvent : public UIEvent { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = InputEvent*; + + static InputEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + static InputEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<InputEventInit>& initializer, + ExceptionState& exception_state); + + explicit InputEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + explicit InputEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<InputEventInit>& initializer, + ExceptionState& exception_state); + + explicit InputEvent(ExecutingContext* context, const AtomicString& type, NativeInputEvent* native_input_event); + + const AtomicString& inputType() const; + const AtomicString& data() const; + + bool IsInputEvent() const override; + + private: + AtomicString input_type_; + AtomicString data_; +}; + +} // namespace webf + +#endif // BRIDGE_INPUT_EVENT_H diff --git a/bridge/core/events/input_event_init.d.ts b/bridge/core/events/input_event_init.d.ts new file mode 100644 index 0000000000..4ba210395f --- /dev/null +++ b/bridge/core/events/input_event_init.d.ts @@ -0,0 +1,8 @@ +import {UIEventInit} from "./ui_event_init"; + +// @ts-ignore +@Dictionary() +export interface InputEventInit extends UIEventInit { + inputType?: string; + data?: string; +} diff --git a/bridge/core/events/intersection_change_event.cc b/bridge/core/events/intersection_change_event.cc new file mode 100644 index 0000000000..dd4fd78e75 --- /dev/null +++ b/bridge/core/events/intersection_change_event.cc @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "intersection_change_event.h" +#include "qjs_intersection_change_event.h" + +namespace webf { + +IntersectionChangeEvent* IntersectionChangeEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { + return MakeGarbageCollected<IntersectionChangeEvent>(context, type, exception_state); +} + +IntersectionChangeEvent* IntersectionChangeEvent::Create( + ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<IntersectionChangeEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<IntersectionChangeEvent>(context, type, initializer, exception_state); +} + +IntersectionChangeEvent::IntersectionChangeEvent(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) + : Event(context, type) {} + +IntersectionChangeEvent::IntersectionChangeEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<IntersectionChangeEventInit>& initializer, + ExceptionState& exception_state) + : Event(context, type), + intersection_ratio_(initializer->hasIntersectionRatio() ? initializer->intersectionRatio() : 0.0) {} + +IntersectionChangeEvent::IntersectionChangeEvent(ExecutingContext* context, + const AtomicString& type, + NativeIntersectionChangeEvent* native_intersection_change_event) + : Event(context, type, &native_intersection_change_event->native_event), + intersection_ratio_(native_intersection_change_event->intersectionRatio) {} + +double IntersectionChangeEvent::intersectionRatio() const { + return intersection_ratio_; +} + +bool IntersectionChangeEvent::IsIntersectionchangeEvent() const { + return true; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/intersection_change_event.d.ts b/bridge/core/events/intersection_change_event.d.ts new file mode 100644 index 0000000000..398936230e --- /dev/null +++ b/bridge/core/events/intersection_change_event.d.ts @@ -0,0 +1,7 @@ +import {Event} from "../dom/events/event"; +import {IntersectionChangeEventInit} from "./intersection_change_event_init"; + +interface IntersectionChangeEvent extends Event { + readonly intersectionRatio: number; + new(type: string, init?: IntersectionChangeEventInit): IntersectionChangeEventInit; +} diff --git a/bridge/core/events/intersection_change_event.h b/bridge/core/events/intersection_change_event.h new file mode 100644 index 0000000000..8feee073d3 --- /dev/null +++ b/bridge/core/events/intersection_change_event.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_INTERSECTION_CHANGE_EVENT_H +#define BRIDGE_INTERSECTION_CHANGE_EVENT_H + +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/source_location.h" +#include "core/dom/events/event.h" +#include "qjs_intersection_change_event_init.h" + +namespace webf { + +struct NativeIntersectionChangeEvent; + +class IntersectionChangeEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = IntersectionChangeEvent*; + + static IntersectionChangeEvent* Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state); + + static IntersectionChangeEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<IntersectionChangeEventInit>& initializer, + ExceptionState& exception_state); + + explicit IntersectionChangeEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<IntersectionChangeEventInit>& initializer, + ExceptionState& exception_state); + + explicit IntersectionChangeEvent(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state); + + explicit IntersectionChangeEvent(ExecutingContext* context, + const AtomicString& type, + NativeIntersectionChangeEvent* native_intersectionchange_event); + + double intersectionRatio() const; + + bool IsIntersectionchangeEvent() const override; + + private: + double intersection_ratio_; +}; + +} // namespace webf + +#endif // BRIDGE_INTERSECTION_CHANGE_EVENT_H diff --git a/bridge/core/events/intersection_change_event_init.d.ts b/bridge/core/events/intersection_change_event_init.d.ts new file mode 100644 index 0000000000..4178e2073d --- /dev/null +++ b/bridge/core/events/intersection_change_event_init.d.ts @@ -0,0 +1,7 @@ +import {UIEventInit} from "./ui_event_init"; + +// @ts-ignore +@Dictionary() +export interface IntersectionChangeEventInit extends UIEventInit { + intersectionRatio?: number; +} diff --git a/bridge/core/events/keyboard_event.cc b/bridge/core/events/keyboard_event.cc new file mode 100644 index 0000000000..a394e60b05 --- /dev/null +++ b/bridge/core/events/keyboard_event.cc @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "keyboard_event.h" +#include "qjs_keyboard_event.h" + +namespace webf { + +double KeyboardEvent::DOM_KEY_LOCATION_LEFT = KeyLocationCode::kDomKeyLocationLeft; +double KeyboardEvent::DOM_KEY_LOCATION_RIGHT = KeyLocationCode::kDomKeyLocationRight; +double KeyboardEvent::DOM_KEY_LOCATION_STANDARD = KeyLocationCode::kDomKeyLocationStandard; +double KeyboardEvent::DOM_KEY_LOCATION_NUMPAD = KeyLocationCode::kDomKeyLocationNumpad; + +KeyboardEvent* KeyboardEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { + return MakeGarbageCollected<KeyboardEvent>(context, type, exception_state); +} + +KeyboardEvent* KeyboardEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<KeyboardEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<KeyboardEvent>(context, type, initializer, exception_state); +} + +KeyboardEvent::KeyboardEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : UIEvent(context, type, exception_state) {} + +KeyboardEvent::KeyboardEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<KeyboardEventInit>& initializer, + ExceptionState& exception_state) + : UIEvent(context, type, initializer, exception_state), + alt_key_(initializer->hasAltKey() && initializer->altKey()), + char_code_(initializer->hasCharCode() ? initializer->charCode() : 0.0), + code_(initializer->hasCode() ? initializer->code() : AtomicString::Empty()), + ctrl_key_(initializer->hasCtrlKey() && initializer->ctrlKey()), + is_composing_(initializer->hasComposed() && initializer->isComposing()), + key_(initializer->hasKey() ? initializer->key() : AtomicString::Empty()), + key_code_(initializer->hasKeyCode() ? initializer->keyCode() : 0.0), + location_(initializer->hasLocation() ? initializer->location() : 0.0), + meta_key_(initializer->hasMetaKey() && initializer->metaKey()), + repeat_(initializer->hasRepeat() && initializer->repeat()), + shift_key_(initializer->hasShiftKey() && initializer->shiftKey()) {} + +KeyboardEvent::KeyboardEvent(ExecutingContext* context, + const AtomicString& type, + NativeKeyboardEvent* native_keyboard_event) + : UIEvent(context, type, &native_keyboard_event->native_event), + alt_key_(native_keyboard_event->altKey), + char_code_(native_keyboard_event->charCode), +#if ANDROID_32_BIT + code_(AtomicString(ctx(), reinterpret_cast<NativeString*>(native_keyboard_event->code))), + key_(AtomicString(ctx(), reinterpret_cast<NativeString*>(native_keyboard_event->key))), +#else + code_(AtomicString(ctx(), native_keyboard_event->code)), + key_(AtomicString(ctx(), native_keyboard_event->key)), +#endif + ctrl_key_(native_keyboard_event->ctrlKey), + is_composing_(native_keyboard_event->isComposing), + key_code_(native_keyboard_event->keyCode), + location_(native_keyboard_event->location), + meta_key_(native_keyboard_event->metaKey), + repeat_(native_keyboard_event->repeat), + shift_key_(native_keyboard_event->shiftKey) { +} + +bool KeyboardEvent::getModifierState(const AtomicString& key_args, ExceptionState& exception_state) { + return false; +} + +bool KeyboardEvent::altKey() const { + return alt_key_; +} + +double KeyboardEvent::charCode() const { + return char_code_; +} + +const AtomicString& KeyboardEvent::code() const { + return code_; +} + +bool KeyboardEvent::ctrlKey() const { + return ctrl_key_; +} + +bool KeyboardEvent::isComposing() const { + return is_composing_; +} + +const AtomicString& KeyboardEvent::key() const { + return key_; +} + +double KeyboardEvent::keyCode() const { + return key_code_; +} + +double KeyboardEvent::location() const { + return location_; +} + +bool KeyboardEvent::metaKey() const { + return meta_key_; +} + +bool KeyboardEvent::repeat() const { + return repeat_; +} + +bool KeyboardEvent::shiftKey() const { + return shift_key_; +} + +bool KeyboardEvent::IsKeyboardEvent() const { + return true; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/keyboard_event.d.ts b/bridge/core/events/keyboard_event.d.ts new file mode 100644 index 0000000000..79b03b0ff3 --- /dev/null +++ b/bridge/core/events/keyboard_event.d.ts @@ -0,0 +1,26 @@ +import {UIEvent} from "./ui_event"; +import {KeyboardEventInit} from "./keyboard_event_init"; + +/** KeyboardEvent objects describe a user interaction with the keyboard; each event describes a single interaction between the user and a key (or combination of a key with modifier keys) on the keyboard. */ +interface KeyboardEvent extends UIEvent { + readonly altKey: boolean; + /** @deprecated */ + readonly charCode: number; + readonly code: string; + readonly ctrlKey: boolean; + readonly isComposing: boolean; + readonly key: string; + /** @deprecated */ + readonly keyCode: number; + readonly location: number; + readonly metaKey: boolean; + readonly repeat: boolean; + readonly shiftKey: boolean; + // getModifierState(keyArg: string): boolean; + readonly DOM_KEY_LOCATION_LEFT: StaticMember<number>; + readonly DOM_KEY_LOCATION_NUMPAD: StaticMember<number>; + readonly DOM_KEY_LOCATION_RIGHT: StaticMember<number>; + readonly DOM_KEY_LOCATION_STANDARD: StaticMember<number>; + + new(type: string, init?: KeyboardEventInit): KeyboardEvent; +} \ No newline at end of file diff --git a/bridge/core/events/keyboard_event.h b/bridge/core/events/keyboard_event.h new file mode 100644 index 0000000000..2cafeea526 --- /dev/null +++ b/bridge/core/events/keyboard_event.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_EVENTS_KEYBOARD_EVENT_H_ +#define BRIDGE_CORE_EVENTS_KEYBOARD_EVENT_H_ + +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/source_location.h" +#include "qjs_keyboard_event_init.h" +#include "ui_event.h" + +namespace webf { + +struct NativeKeyboardEvent; + +class KeyboardEvent : public UIEvent { + DEFINE_WRAPPERTYPEINFO(); + + public: + enum KeyLocationCode { + kDomKeyLocationStandard = 0x00, + kDomKeyLocationLeft = 0x01, + kDomKeyLocationRight = 0x02, + kDomKeyLocationNumpad = 0x03 + }; + using ImplType = KeyboardEvent*; + + static double DOM_KEY_LOCATION_LEFT; + static double DOM_KEY_LOCATION_RIGHT; + static double DOM_KEY_LOCATION_NUMPAD; + static double DOM_KEY_LOCATION_STANDARD; + + static KeyboardEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + static KeyboardEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<KeyboardEventInit>& initializer, + ExceptionState& exception_state); + + explicit KeyboardEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + explicit KeyboardEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<KeyboardEventInit>& initializer, + ExceptionState& exception_state); + + explicit KeyboardEvent(ExecutingContext* context, + const AtomicString& type, + NativeKeyboardEvent* native_keyboard_event); + + bool altKey() const; + double charCode() const; + const AtomicString& code() const; + bool ctrlKey() const; + bool isComposing() const; + const AtomicString& key() const; + double keyCode() const; + double location() const; + bool metaKey() const; + bool repeat() const; + bool shiftKey() const; + + bool getModifierState(const AtomicString& key_args, ExceptionState& exception_state); + + bool IsKeyboardEvent() const override; + + private: + bool alt_key_; + double char_code_; + AtomicString code_; + bool ctrl_key_; + bool is_composing_; + AtomicString key_; + double key_code_; + double location_; + bool meta_key_; + bool repeat_; + bool shift_key_; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_EVENTS_KEYBOARD_EVENT_H_ diff --git a/bridge/core/events/keyboard_event_init.d.ts b/bridge/core/events/keyboard_event_init.d.ts new file mode 100644 index 0000000000..18c5cb8d81 --- /dev/null +++ b/bridge/core/events/keyboard_event_init.d.ts @@ -0,0 +1,19 @@ +import {UIEventInit} from "./ui_event_init"; + +// @ts-ignore +@Dictionary() +export interface KeyboardEventInit extends UIEventInit { + altKey?: boolean; + /** @deprecated */ + charCode?: number; + code?: string; + ctrlKey?: boolean; + isComposing?: boolean; + key?: string; + /** @deprecated */ + keyCode?: number; + location?: number; + metaKey?: boolean; + repeat?: boolean; + shiftKey?: boolean; +} diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc new file mode 100644 index 0000000000..4fc16d6e6f --- /dev/null +++ b/bridge/core/events/message_event.cc @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "message_event.h" +#include "core/dom/events/event.h" +#include "qjs_message_event.h" + +namespace webf { + +MessageEvent* MessageEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { + return MakeGarbageCollected<MessageEvent>(context, type, exception_state); +} + +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, ExceptionState& exception_state) + : Event(context, type) {} + +MessageEvent::MessageEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MessageEventInit>& init) + : Event(context, type), + data_(init->hasData() ? init->data() : ScriptValue::Empty(ctx())), + origin_(init->hasOrigin() ? init->origin() : AtomicString::Empty()), + lastEventId_(init->hasLastEventId() ? init->lastEventId() : AtomicString::Empty()), + source_(init->hasSource() ? init->source() : AtomicString::Empty()) {} + +MessageEvent::MessageEvent(ExecutingContext* context, + const AtomicString& type, + NativeMessageEvent* native_message_event) + : Event(context, type, &native_message_event->native_event), +#if ANDROID_32_BIT + data_(ScriptValue(ctx(), *(reinterpret_cast<NativeValue*>(native_message_event->data)))), + origin_(AtomicString(ctx(), reinterpret_cast<NativeString*>(native_message_event->origin))), + lastEventId_(AtomicString(ctx(), reinterpret_cast<NativeString*>(native_message_event->lastEventId))), + source_(AtomicString(ctx(), reinterpret_cast<NativeString*>(native_message_event->source))) +#else + data_(ScriptValue(ctx(), *(static_cast<NativeValue*>(native_message_event->data)))), + origin_(AtomicString(ctx(), native_message_event->origin)), + lastEventId_(AtomicString(ctx(), native_message_event->lastEventId)), + source_(AtomicString(ctx(), native_message_event->source)) +#endif + +{ +} + +ScriptValue MessageEvent::data() const { + return data_; +} + +AtomicString MessageEvent::origin() const { + return origin_; +} + +AtomicString MessageEvent::lastEventId() const { + return lastEventId_; +} + +AtomicString MessageEvent::source() const { + return source_; +} + +bool MessageEvent::IsMessageEvent() const { + return true; +} + +} // namespace webf 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..fe56ee9dfa --- /dev/null +++ b/bridge/core/events/message_event.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ +#define BRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ + +#include "core/dom/events/event.h" +#include "qjs_message_event_init.h" + +namespace webf { + +struct NativeMessageEvent; + +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, ExceptionState& exception_state); + explicit MessageEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MessageEventInit>& init); + explicit MessageEvent(ExecutingContext* context, const AtomicString& type, NativeMessageEvent* native_message_event); + + ScriptValue data() const; + AtomicString origin() const; + AtomicString lastEventId() const; + AtomicString source() const; + + bool IsMessageEvent() const override; + + private: + ScriptValue data_; + AtomicString origin_; + AtomicString lastEventId_; + AtomicString source_; +}; + +} // namespace webf + +#endif // BRIDGE_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..90e35c08bc --- /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/core/events/mouse_event.cc b/bridge/core/events/mouse_event.cc new file mode 100644 index 0000000000..8908433c4e --- /dev/null +++ b/bridge/core/events/mouse_event.cc @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "mouse_event.h" +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "qjs_mouse_event.h" + +namespace webf { + +MouseEvent* MouseEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { + return MakeGarbageCollected<MouseEvent>(context, type, exception_state); +} + +MouseEvent* MouseEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MouseEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<MouseEvent>(context, type, initializer, exception_state); +} + +MouseEvent::MouseEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : UIEvent(context, type, exception_state) {} + +MouseEvent::MouseEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MouseEventInit>& initializer, + ExceptionState& exception_state) + : UIEvent(context, type, initializer, exception_state) +// alt_key_(initializer->altKey()), +// button_(initializer->button()), +// buttons_(initializer->buttons()), +// client_x_(initializer->clientX()), +// client_y_(initializer->clientY()), +// ctrl_key_(initializer->ctrlKey()), +// meta_key_(initializer->metaKey()), +// screen_x_(initializer->screenX()), +// screen_y_(initializer->screenY()), +// shift_key_(initializer->shiftKey()), +// related_target_(initializer->relatedTarget()) {} +{} + +MouseEvent::MouseEvent(ExecutingContext* context, const AtomicString& type, NativeMouseEvent* native_mouse_event) + : UIEvent(context, type, &native_mouse_event->native_event), + // alt_key_(native_mouse_event->altKey), + // button_(native_mouse_event->button), + // buttons_(native_mouse_event->buttons), + client_x_(native_mouse_event->clientX), + client_y_(native_mouse_event->clientY), + // ctrl_key_(native_mouse_event->ctrlKey), + // meta_key_(native_mouse_event->metaKey), + // movement_x_(native_mouse_event->movementX), + // movement_y_(native_mouse_event->movementY), + offset_x_(native_mouse_event->offsetX), + offset_y_(native_mouse_event->offsetY) +// page_x_(native_mouse_event->pageX), +// page_y_(native_mouse_event->pageY), +// screen_x_(native_mouse_event->screenX), +// screen_y_(native_mouse_event->screenY), +// shift_key_(native_mouse_event->shiftKey), +// x_(native_mouse_event->x), +// y_(native_mouse_event->y) +{} + +bool MouseEvent::altKey() const { + return alt_key_; +}; +double MouseEvent::button() const { + return button_; +}; +double MouseEvent::buttons() const { + return buttons_; +}; +double MouseEvent::clientX() const { + return client_x_; +}; +double MouseEvent::clientY() const { + return client_y_; +}; +bool MouseEvent::ctrlKey() const { + return ctrl_key_; +}; +bool MouseEvent::metaKey() const { + return meta_key_; +}; +double MouseEvent::movementX() const { + return movement_x_; +}; +double MouseEvent::movementY() const { + return movement_y_; +}; +double MouseEvent::offsetX() const { + return offset_x_; +}; +double MouseEvent::offsetY() const { + return offset_y_; +}; +double MouseEvent::pageX() const { + return page_x_; +}; +double MouseEvent::pageY() const { + return page_y_; +}; +double MouseEvent::screenX() const { + return screen_x_; +}; +double MouseEvent::screenY() const { + return screen_y_; +}; +bool MouseEvent::shiftKey() const { + return shift_key_; +}; +double MouseEvent::x() const { + return x_; +}; +double MouseEvent::y() const { + return y_; +}; + +EventTarget* MouseEvent::relatedTarget() const { + return related_target_; +} + +bool MouseEvent::IsMouseEvent() const { + return true; +} + +void MouseEvent::Trace(GCVisitor* visitor) const { + visitor->Trace(related_target_); + UIEvent::Trace(visitor); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/mouse_event.d.ts b/bridge/core/events/mouse_event.d.ts new file mode 100644 index 0000000000..3f49a11bbf --- /dev/null +++ b/bridge/core/events/mouse_event.d.ts @@ -0,0 +1,27 @@ +import {UIEvent} from "./ui_event"; +import {EventTarget} from "../dom/events/event_target"; +import {MouseEventInit} from "./mouse_event_init"; + +/** Events that occur due to the user interacting with a pointing device (such as a mouse). Common events using this interface include click, dblclick, mouseup, mousedown. */ +interface MouseEvent extends UIEvent { + // readonly altKey: boolean; + // readonly button: number; + // readonly buttons: number; + readonly clientX: number; + readonly clientY: number; + // readonly ctrlKey: boolean; + // readonly metaKey: boolean; + // readonly movementX: number; + // readonly movementY: number; + readonly offsetX: number; + readonly offsetY: number; + // readonly pageX: number; + // readonly pageY: number; + // readonly relatedTarget: EventTarget | null; + // readonly screenX: number; + // readonly screenY: number; + // readonly shiftKey: boolean; + // readonly x: number; + // readonly y: number; + new(type: string, init?: MouseEventInit): MouseEvent; +} \ No newline at end of file diff --git a/bridge/core/events/mouse_event.h b/bridge/core/events/mouse_event.h new file mode 100644 index 0000000000..46191c7d9f --- /dev/null +++ b/bridge/core/events/mouse_event.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_EVENTS_MOUSE_EVENT_H_ +#define WEBF_CORE_EVENTS_MOUSE_EVENT_H_ + +#include "qjs_mouse_event_init.h" +#include "ui_event.h" + +namespace webf { + +struct NativeMouseEvent; + +class MouseEvent : public UIEvent { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = MouseEvent*; + + static MouseEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + static MouseEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MouseEventInit>& initializer, + ExceptionState& exception_state); + + explicit MouseEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + explicit MouseEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MouseEventInit>& initializer, + ExceptionState& exception_state); + + explicit MouseEvent(ExecutingContext* context, const AtomicString& type, NativeMouseEvent* native_mouse_event); + + bool altKey() const; + double button() const; + double buttons() const; + double clientX() const; + double clientY() const; + bool ctrlKey() const; + bool metaKey() const; + double movementX() const; + double movementY() const; + double offsetX() const; + double offsetY() const; + double pageX() const; + double pageY() const; + double screenX() const; + double screenY() const; + bool shiftKey() const; + double x() const; + double y() const; + EventTarget* relatedTarget() const; + + void Trace(GCVisitor* visitor) const override; + + bool IsMouseEvent() const override; + + private: + bool alt_key_; + double button_; + double buttons_; + double client_x_; + double client_y_; + bool ctrl_key_; + bool meta_key_; + double movement_x_; + double movement_y_; + double offset_x_; + double offset_y_; + double page_x_; + double page_y_; + double screen_x_; + double screen_y_; + bool shift_key_; + double x_; + double y_; + Member<EventTarget> related_target_; +}; + +} // namespace webf + +#endif // WEBF_CORE_EVENTS_TOUCH_EVENT_H_ diff --git a/bridge/core/events/mouse_event_init.d.ts b/bridge/core/events/mouse_event_init.d.ts new file mode 100644 index 0000000000..42307db321 --- /dev/null +++ b/bridge/core/events/mouse_event_init.d.ts @@ -0,0 +1,18 @@ +import {EventTarget} from "../dom/events/event_target"; +import {UIEventInit} from "./ui_event_init"; + +// @ts-ignore +@Dictionary() +export interface MouseEventInit extends UIEventInit { + // altKey?: boolean; + // button?: number; + // buttons?: number; + // clientX?: number; + // clientY?: number; + // ctrlKey?: boolean; + // metaKey?: boolean; + // relatedTarget?: EventTarget | null; + // screenX?: number; + // screenY?: number; + // shiftKey?: boolean; +} \ No newline at end of file diff --git a/bridge/core/events/pointer_event.cc b/bridge/core/events/pointer_event.cc new file mode 100644 index 0000000000..339206fc4d --- /dev/null +++ b/bridge/core/events/pointer_event.cc @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "pointer_event.h" +#include "qjs_pointer_event.h" + +namespace webf { + +PointerEvent* PointerEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { + return MakeGarbageCollected<PointerEvent>(context, type, exception_state); +} + +PointerEvent* PointerEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<PointerEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<PointerEvent>(context, type, initializer, exception_state); +} + +PointerEvent::PointerEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : MouseEvent(context, type, exception_state) {} + +PointerEvent::PointerEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<PointerEventInit>& initializer, + ExceptionState& exception_state) + : MouseEvent(context, type, initializer, exception_state), + height_(initializer->hasHeight() ? initializer->height() : 0.0), + is_primary(initializer->hasIsPrimary() && initializer->isPrimary()), + pointer_id_(initializer->hasPointerId() ? initializer->pointerId() : 0.0), + pointer_type_(initializer->hasPointerType() ? initializer->pointerType() : AtomicString::Empty()), + pressure_(initializer->hasPressure() ? initializer->pressure() : 0.0), + tangential_pressure_(initializer->hasTangentialPressure() ? initializer->tangentialPressure() : 0.0), + tilt_x_(initializer->hasTiltX() ? initializer->tiltX() : 0.0), + tilt_y_(initializer->hasTiltY() ? initializer->tiltY() : 0.0), + twist_(initializer->hasTwist() ? initializer->twist() : 0.0), + width_(initializer->hasWidth() ? initializer->width() : 0.0) {} + +PointerEvent::PointerEvent(ExecutingContext* context, + const AtomicString& type, + NativePointerEvent* native_pointer_event) + : MouseEvent(context, type, &native_pointer_event->native_event), + height_(native_pointer_event->height), + is_primary(native_pointer_event->isPrimary), + pointer_id_(native_pointer_event->pointerId), +#if ANDROID_32_BIT + pointer_type_(AtomicString(ctx(), reinterpret_cast<NativeString*>(native_pointer_event->pointerType))), +#else + pointer_type_(AtomicString(ctx(), native_pointer_event->pointerType)), +#endif + pressure_(native_pointer_event->pressure), + tangential_pressure_(native_pointer_event->tangentialPressure), + tilt_x_(native_pointer_event->tiltX), + tilt_y_(native_pointer_event->tiltY), + twist_(native_pointer_event->twist), + width_(native_pointer_event->width) { +} + +double PointerEvent::height() const { + return height_; +}; +bool PointerEvent::isPrimary() const { + return is_primary; +}; +double PointerEvent::pointerId() const { + return pointer_id_; +}; +AtomicString PointerEvent::pointerType() const { + return pointer_type_; +}; +double PointerEvent::pressure() const { + return pressure_; +}; +double PointerEvent::tangentialPressure() const { + return tangential_pressure_; +}; +double PointerEvent::tiltX() const { + return tilt_x_; +}; +double PointerEvent::tiltY() const { + return tilt_y_; +}; +double PointerEvent::twist() const { + return twist_; +}; +double PointerEvent::width() const { + return width_; +}; + +bool PointerEvent::IsPointerEvent() const { + return true; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/pointer_event.d.ts b/bridge/core/events/pointer_event.d.ts new file mode 100644 index 0000000000..64f104e265 --- /dev/null +++ b/bridge/core/events/pointer_event.d.ts @@ -0,0 +1,17 @@ +import {MouseEvent} from "./mouse_event"; +import {PointerEventInit} from "./pointer_event_init"; + +/** The state of a DOM event produced by a pointer such as the geometry of the contact point, the device type that generated the event, the amount of pressure that was applied on the contact surface, etc. */ +interface PointerEvent extends MouseEvent { + readonly height: number; + readonly isPrimary: boolean; + readonly pointerId: number; + readonly pointerType: string; + readonly pressure: number; + readonly tangentialPressure: number; + readonly tiltX: number; + readonly tiltY: number; + readonly twist: number; + readonly width: number; + new(type: string, init?: PointerEventInit): PointerEvent; +} \ No newline at end of file diff --git a/bridge/core/events/pointer_event.h b/bridge/core/events/pointer_event.h new file mode 100644 index 0000000000..02efbb0b40 --- /dev/null +++ b/bridge/core/events/pointer_event.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_EVENTS_POINTER_EVENT_H_ +#define WEBF_CORE_EVENTS_POINTER_EVENT_H_ + +#include "mouse_event.h" +#include "qjs_pointer_event_init.h" + +namespace webf { + +struct NativePointerEvent; + +class PointerEvent : public MouseEvent { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = UIEvent*; + + static PointerEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + static PointerEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<PointerEventInit>& initializer, + ExceptionState& exception_state); + + explicit PointerEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + explicit PointerEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<PointerEventInit>& initializer, + ExceptionState& exception_state); + + explicit PointerEvent(ExecutingContext* context, const AtomicString& type, NativePointerEvent* native_pointer_event); + + double height() const; + bool isPrimary() const; + double pointerId() const; + AtomicString pointerType() const; + double pressure() const; + double tangentialPressure() const; + double tiltX() const; + double tiltY() const; + double twist() const; + double width() const; + + bool IsPointerEvent() const override; + + private: + double height_; + bool is_primary; + double pointer_id_; + AtomicString pointer_type_; + double pressure_; + double tangential_pressure_; + double tilt_x_; + double tilt_y_; + double twist_; + double width_; +}; + +} // namespace webf + +#endif // WEBF_CORE_EVENTS_TOUCH_EVENT_H_ diff --git a/bridge/core/events/pointer_event_init.d.ts b/bridge/core/events/pointer_event_init.d.ts new file mode 100644 index 0000000000..47e56062ad --- /dev/null +++ b/bridge/core/events/pointer_event_init.d.ts @@ -0,0 +1,16 @@ +import {MouseEventInit} from "./mouse_event_init"; + +// @ts-ignore +@Dictionary() +export interface PointerEventInit extends MouseEventInit { + isPrimary?: boolean; + pointerId?: number; + pointerType?: string; + pressure?: number; + tangentialPressure?: number; + tiltX?: number; + tiltY?: number; + twist?: number; + width?: number; + height?: number; +} \ No newline at end of file diff --git a/bridge/core/events/pop_state_event.cc b/bridge/core/events/pop_state_event.cc new file mode 100644 index 0000000000..da902dc9c2 --- /dev/null +++ b/bridge/core/events/pop_state_event.cc @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "pop_state_event.h" +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "event_type_names.h" +#include "qjs_pop_state_event.h" + +namespace webf { + +PopStateEvent* PopStateEvent::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected<PopStateEvent>(context, event_type_names::kpopstate, exception_state); +} + +PopStateEvent* PopStateEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<PopStateEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<PopStateEvent>(context, type, initializer, exception_state); +} + +PopStateEvent::PopStateEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : Event(context, type) {} + +PopStateEvent::PopStateEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<PopStateEventInit>& initializer, + ExceptionState& exception_state) + : Event(context, type), state_(initializer->hasState() ? initializer->state() : ScriptValue::Empty(ctx())) {} + +PopStateEvent::PopStateEvent(ExecutingContext* context, const AtomicString& type, NativePopStateEvent* native_ui_event) + : Event(context, type, &native_ui_event->native_event), +#if ANDROID_32_BIT + state_(ScriptValue::CreateJsonObject(context->ctx(), + reinterpret_cast<const char*>(native_ui_event->state), + strlen(reinterpret_cast<const char*>(native_ui_event->state)))) +#else + state_(ScriptValue::CreateJsonObject(context->ctx(), + static_cast<const char*>(native_ui_event->state), + strlen(static_cast<const char*>(native_ui_event->state)))) +#endif +{ +} + +ScriptValue PopStateEvent::state() const { + return state_; +} + +bool PopStateEvent::IsPopstateEvent() const { + return true; +} + +void PopStateEvent::Trace(GCVisitor* visitor) const { + state_.Trace(visitor); + Event::Trace(visitor); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/pop_state_event.d.ts b/bridge/core/events/pop_state_event.d.ts new file mode 100644 index 0000000000..3d7b9bf4d1 --- /dev/null +++ b/bridge/core/events/pop_state_event.d.ts @@ -0,0 +1,6 @@ +import {Event} from "../dom/events/event"; + +interface PopStateEvent extends Event { + readonly state: any; + new(): PopStateEvent; +} \ No newline at end of file diff --git a/bridge/core/events/pop_state_event.h b/bridge/core/events/pop_state_event.h new file mode 100644 index 0000000000..2bf71023fd --- /dev/null +++ b/bridge/core/events/pop_state_event.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_EVENTS_POP_STATE_EVENT_H_ +#define WEBF_CORE_EVENTS_POP_STATE_EVENT_H_ + +#include "core/dom/events/event.h" +#include "qjs_pop_state_event_init.h" + +namespace webf { + +struct NativePopStateEvent; + +class PopStateEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = PopStateEvent*; + + static PopStateEvent* Create(ExecutingContext* context, ExceptionState& exception_state); + + static PopStateEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<PopStateEventInit>& initializer, + ExceptionState& exception_state); + + explicit PopStateEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + explicit PopStateEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<PopStateEventInit>& initializer, + ExceptionState& exception_state); + + explicit PopStateEvent(ExecutingContext* context, const AtomicString& type, NativePopStateEvent* native_ui_event); + + ScriptValue state() const; + + bool IsPopstateEvent() const override; + + void Trace(GCVisitor* visitor) const override; + + private: + ScriptValue state_; +}; + +template <> +struct DowncastTraits<PopStateEvent> { + static bool AllowFrom(const Event& event) { return event.IsPopstateEvent(); } +}; + +} // namespace webf + +#endif // WEBF_CORE_EVENTS_POP_STATE_EVENT_H_ diff --git a/bridge/core/events/pop_state_event_init.d.ts b/bridge/core/events/pop_state_event_init.d.ts new file mode 100644 index 0000000000..706cd7beac --- /dev/null +++ b/bridge/core/events/pop_state_event_init.d.ts @@ -0,0 +1,7 @@ +import {EventInit} from "../dom/events/event_init"; + +// @ts-ignore +@Dictionary() +export interface PopStateEventInit extends EventInit { + state?: any; +} \ No newline at end of file diff --git a/bridge/core/events/promise_rejection_event.cc b/bridge/core/events/promise_rejection_event.cc new file mode 100644 index 0000000000..2f7bfc1ecb --- /dev/null +++ b/bridge/core/events/promise_rejection_event.cc @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "promise_rejection_event.h" +#include "event_type_names.h" + +namespace webf { + +PromiseRejectionEvent* PromiseRejectionEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { + return MakeGarbageCollected<PromiseRejectionEvent>(context, type, exception_state); +} + +PromiseRejectionEvent* PromiseRejectionEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<PromiseRejectionEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<PromiseRejectionEvent>(context, type, initializer, exception_state); +} + +PromiseRejectionEvent::PromiseRejectionEvent(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) + : Event(context, type) {} + +PromiseRejectionEvent::PromiseRejectionEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<PromiseRejectionEventInit>& initializer, + ExceptionState& exception_state) + : Event(context, type), + reason_(initializer->hasReason() ? initializer->reason() : ScriptValue::Empty(ctx())), + promise_(initializer->hasPromise() ? initializer->promise() : ScriptValue::Empty(ctx())) {} + +bool PromiseRejectionEvent::IsPromiseRejectionEvent() const { + return true; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/promise_rejection_event.d.ts b/bridge/core/events/promise_rejection_event.d.ts new file mode 100644 index 0000000000..192bad7a17 --- /dev/null +++ b/bridge/core/events/promise_rejection_event.d.ts @@ -0,0 +1,8 @@ +import {Event} from "../dom/events/event"; +import {PromiseRejectionEventInit} from "./promise_rejection_event_init"; + +interface PromiseRejectionEvent extends Event { + readonly promise: any; + readonly reason: any; + new(eventType: string, init?: PromiseRejectionEventInit) : PromiseRejectionEvent; +} diff --git a/bridge/core/events/promise_rejection_event.h b/bridge/core/events/promise_rejection_event.h new file mode 100644 index 0000000000..f32f1546ce --- /dev/null +++ b/bridge/core/events/promise_rejection_event.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_EVENTS_PROMISE_REJECTION_EVENT_H_ +#define BRIDGE_CORE_EVENTS_PROMISE_REJECTION_EVENT_H_ + +#include "core/dom/events/event.h" +#include "qjs_promise_rejection_event_init.h" + +namespace webf { + +class PromiseRejectionEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = ErrorEvent*; + static PromiseRejectionEvent* Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state); + static PromiseRejectionEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<PromiseRejectionEventInit>& initializer, + ExceptionState& exception_state); + + explicit PromiseRejectionEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + explicit PromiseRejectionEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<PromiseRejectionEventInit>& initializer, + ExceptionState& exception_state); + + ScriptValue promise() { return promise_; } + ScriptValue reason() { return reason_; } + + bool IsPromiseRejectionEvent() const override; + + private: + ScriptValue promise_; + ScriptValue reason_; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_EVENTS_PROMISE_REJECTION_EVENT_H_ diff --git a/bridge/core/events/promise_rejection_event_init.d.ts b/bridge/core/events/promise_rejection_event_init.d.ts new file mode 100644 index 0000000000..f58d3dc49d --- /dev/null +++ b/bridge/core/events/promise_rejection_event_init.d.ts @@ -0,0 +1,8 @@ +import { EventInit } from "../dom/events/event_init"; + +// @ts-ignore +@Dictionary() +export interface PromiseRejectionEventInit extends EventInit { + promise: any; + reason: any; +} diff --git a/bridge/core/events/touch_event.cc b/bridge/core/events/touch_event.cc new file mode 100644 index 0000000000..04afff03e2 --- /dev/null +++ b/bridge/core/events/touch_event.cc @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "touch_event.h" +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "qjs_touch_event.h" + +namespace webf { + +TouchEvent* TouchEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { + return MakeGarbageCollected<TouchEvent>(context, type, exception_state); +} + +TouchEvent* TouchEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<TouchEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<TouchEvent>(context, type, initializer, exception_state); +} + +TouchEvent::TouchEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : UIEvent(context, type, exception_state) {} + +TouchEvent::TouchEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<TouchEventInit>& initializer, + ExceptionState& exception_state) + : UIEvent(context, type, initializer, exception_state), + alt_key_(initializer->hasAltKey() && initializer->altKey()), + changed_touches_(initializer->hasChangedTouches() ? initializer->changedTouches() : nullptr), + ctrl_key_(initializer->hasCtrlKey() && initializer->ctrlKey()), + meta_key_(initializer->hasMetaKey() && initializer->metaKey()), + shift_key_(initializer->hasShiftKey() && initializer->shiftKey()), + target_touches_(initializer->hasTargetTouches() ? initializer->targetTouches() : nullptr), + touches_(initializer->hasTouches() ? initializer->touches() : nullptr) {} + +TouchEvent::TouchEvent(ExecutingContext* context, const AtomicString& type, NativeTouchEvent* native_touch_event) + : UIEvent(context, type, &native_touch_event->native_event), + alt_key_(native_touch_event->altKey), + ctrl_key_(native_touch_event->ctrlKey), + meta_key_(native_touch_event->metaKey), + shift_key_(native_touch_event->shiftKey), +#if ANDROID_32_BIT + changed_touches_( + MakeGarbageCollected<TouchList>(context, + reinterpret_cast<NativeTouchList*>(native_touch_event->changedTouches))), + target_touches_( + MakeGarbageCollected<TouchList>(context, + reinterpret_cast<NativeTouchList*>(native_touch_event->targetTouches))), + touches_( + MakeGarbageCollected<TouchList>(context, reinterpret_cast<NativeTouchList*>(native_touch_event->touches))) +#else + changed_touches_( + MakeGarbageCollected<TouchList>(context, static_cast<NativeTouchList*>(native_touch_event->changedTouches))), + target_touches_( + MakeGarbageCollected<TouchList>(context, static_cast<NativeTouchList*>(native_touch_event->targetTouches))), + touches_(MakeGarbageCollected<TouchList>(context, static_cast<NativeTouchList*>(native_touch_event->touches))) +#endif +{ +} + +bool TouchEvent::altKey() const { + return alt_key_; +} +bool TouchEvent::ctrlKey() const { + return ctrl_key_; +} + +bool TouchEvent::metaKey() const { + return false; +} + +bool TouchEvent::shiftKey() const { + return shift_key_; +} + +TouchList* TouchEvent::changedTouches() const { + return changed_touches_; +} + +TouchList* TouchEvent::targetTouches() const { + return target_touches_; +} + +void TouchEvent::Trace(GCVisitor* visitor) const { + visitor->Trace(touches_); + visitor->Trace(changed_touches_); + visitor->Trace(target_touches_); + UIEvent::Trace(visitor); +} + +TouchList* TouchEvent::touches() const { + return touches_; +} + +bool TouchEvent::IsTouchEvent() const { + return true; +} + +} // namespace webf diff --git a/bridge/core/events/touch_event.d.ts b/bridge/core/events/touch_event.d.ts new file mode 100644 index 0000000000..d3675f991f --- /dev/null +++ b/bridge/core/events/touch_event.d.ts @@ -0,0 +1,16 @@ +import {UIEvent} from "./ui_event"; +import {EventTarget} from "../dom/events/event_target"; +import {TouchList} from "../input/touch_list"; +import {TouchEventInit} from "./touch_event_init"; + +/** An event sent when the state of contacts with a touch-sensitive surface changes. This surface can be a touch screen or trackpad, for example. The event can describe one or more points of contact with the screen and includes support for detecting movement, addition and removal of contact points, and so forth. */ +interface TouchEvent extends UIEvent { + readonly touches: TouchList; + readonly targetTouches: TouchList; + readonly changedTouches: TouchList; + readonly altKey: boolean; + readonly metaKey: boolean; + readonly ctrlKey: boolean; + readonly shiftKey: boolean; + new(type: string, init?: TouchEventInit): TouchEvent; +} \ No newline at end of file diff --git a/bridge/core/events/touch_event.h b/bridge/core/events/touch_event.h new file mode 100644 index 0000000000..08e9bf2ece --- /dev/null +++ b/bridge/core/events/touch_event.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_EVENTS_TOUCH_EVENT_H_ +#define WEBF_CORE_EVENTS_TOUCH_EVENT_H_ + +#include "core/input/touch_list.h" +#include "qjs_touch_event_init.h" +#include "ui_event.h" + +namespace webf { + +struct NativeTouchEvent; + +class TouchEvent : public UIEvent { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = TouchEvent*; + + static TouchEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + static TouchEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<TouchEventInit>& initializer, + ExceptionState& exception_state); + + explicit TouchEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + explicit TouchEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<TouchEventInit>& initializer, + ExceptionState& exception_state); + + explicit TouchEvent(ExecutingContext* context, const AtomicString& type, NativeTouchEvent* native_touch_event); + + bool altKey() const; + bool ctrlKey() const; + bool metaKey() const; + bool shiftKey() const; + TouchList* changedTouches() const; + TouchList* targetTouches() const; + TouchList* touches() const; + + void Trace(GCVisitor* visitor) const override; + + bool IsTouchEvent() const override; + + private: + bool alt_key_; + bool ctrl_key_; + bool meta_key_; + bool shift_key_; + Member<TouchList> changed_touches_; + Member<TouchList> target_touches_; + Member<TouchList> touches_; +}; + +} // namespace webf + +#endif // WEBF_CORE_EVENTS_TOUCH_EVENT_H_ diff --git a/bridge/core/events/touch_event_init.d.ts b/bridge/core/events/touch_event_init.d.ts new file mode 100644 index 0000000000..d093751686 --- /dev/null +++ b/bridge/core/events/touch_event_init.d.ts @@ -0,0 +1,14 @@ +import {UIEventInit} from "./ui_event_init"; +import {TouchList} from "../input/touch_list"; + +// @ts-ignore +@Dictionary() +export interface TouchEventInit extends UIEventInit { + altKey?: boolean; + changedTouches?: TouchList; + ctrlKey?: boolean; + metaKey?: boolean; + shiftKey?: boolean; + targetTouches?: TouchList; + touches?: TouchList; +} \ No newline at end of file diff --git a/bridge/core/events/transition_event.cc b/bridge/core/events/transition_event.cc new file mode 100644 index 0000000000..0e288bc7b4 --- /dev/null +++ b/bridge/core/events/transition_event.cc @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "transition_event.h" +#include "qjs_transition_event.h" + +namespace webf { + +TransitionEvent* TransitionEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { + return MakeGarbageCollected<TransitionEvent>(context, type, exception_state); +} + +TransitionEvent* TransitionEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<TransitionEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<TransitionEvent>(context, type, initializer, exception_state); +} + +TransitionEvent::TransitionEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : Event(context, type) {} +TransitionEvent::TransitionEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<TransitionEventInit>& initializer, + ExceptionState& exception_state) + : Event(context, type, initializer), + elapsed_time_(initializer->hasElapsedTime() ? initializer->elapsedTime() : 0.0), + property_name_(initializer->hasPropertyName() ? initializer->propertyName() : AtomicString::Empty()), + pseudo_element_(initializer->hasPseudoElement() ? initializer->pseudoElement() : AtomicString::Empty()) {} + +TransitionEvent::TransitionEvent(ExecutingContext* context, + const AtomicString& type, + NativeTransitionEvent* native_transition_event) + : + + Event(context, type, &native_transition_event->native_event), + elapsed_time_(native_transition_event->elapsedTime), +#if ANDROID_32_BIT + property_name_(AtomicString(ctx(), reinterpret_cast<NativeString*>(native_transition_event->propertyName))), + pseudo_element_(AtomicString(ctx(), reinterpret_cast<NativeString*>(native_transition_event->pseudoElement))) +#else + property_name_(AtomicString(ctx(), native_transition_event->propertyName)), + pseudo_element_(AtomicString(ctx(), native_transition_event->pseudoElement)) +#endif +{ +} + +double TransitionEvent::elapsedTime() const { + return elapsed_time_; +} + +AtomicString TransitionEvent::propertyName() const { + return property_name_; +} + +AtomicString TransitionEvent::pseudoElement() const { + return pseudo_element_; +} + +bool TransitionEvent::IsTransitionEvent() const { + return true; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/transition_event.d.ts b/bridge/core/events/transition_event.d.ts new file mode 100644 index 0000000000..f8a6532791 --- /dev/null +++ b/bridge/core/events/transition_event.d.ts @@ -0,0 +1,10 @@ +import {Event} from "../dom/events/event"; +import {TransitionEventInit} from "./transition_event_init"; +/** Events providing information related to transitions. */ + +interface TransitionEvent extends Event { + readonly elapsedTime: number; + readonly propertyName: string; + readonly pseudoElement: string; + new(type: string, init?: TransitionEventInit): TransitionEvent; +} \ No newline at end of file diff --git a/bridge/core/events/transition_event.h b/bridge/core/events/transition_event.h new file mode 100644 index 0000000000..a07f20899a --- /dev/null +++ b/bridge/core/events/transition_event.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_EVENTS_TRANSITION_EVENT_H_ +#define WEBF_CORE_EVENTS_TRANSITION_EVENT_H_ + +#include "core/dom/events/event.h" +#include "qjs_transition_event_init.h" + +namespace webf { + +struct NativeTransitionEvent; + +class TransitionEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = TransitionEvent*; + + static TransitionEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + static TransitionEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<TransitionEventInit>& initializer, + ExceptionState& exception_state); + + explicit TransitionEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + explicit TransitionEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<TransitionEventInit>& initializer, + ExceptionState& exception_state); + + explicit TransitionEvent(ExecutingContext* context, + const AtomicString& type, + NativeTransitionEvent* native_transition_event); + + double elapsedTime() const; + AtomicString propertyName() const; + AtomicString pseudoElement() const; + + bool IsTransitionEvent() const override; + + private: + double elapsed_time_; + AtomicString property_name_; + AtomicString pseudo_element_; +}; + +} // namespace webf + +#endif // WEBF_CORE_EVENTS_TRANSITION_EVENT_H_ diff --git a/bridge/core/events/transition_event_init.d.ts b/bridge/core/events/transition_event_init.d.ts new file mode 100644 index 0000000000..fbe9a70fac --- /dev/null +++ b/bridge/core/events/transition_event_init.d.ts @@ -0,0 +1,9 @@ +import {EventInit} from "../dom/events/event_init"; + +// @ts-ignore +@Dictionary() +export interface TransitionEventInit extends EventInit { + elapsedTime?: number; + propertyName?: string; + pseudoElement?: string; +} \ No newline at end of file diff --git a/bridge/core/events/ui_event.cc b/bridge/core/events/ui_event.cc new file mode 100644 index 0000000000..b01decae06 --- /dev/null +++ b/bridge/core/events/ui_event.cc @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "ui_event.h" +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "core/frame/window.h" +#include "qjs_ui_event.h" + +namespace webf { + +UIEvent* UIEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { + return MakeGarbageCollected<UIEvent>(context, type, exception_state); +} + +UIEvent* UIEvent::Create(ExecutingContext* context, + const AtomicString& type, + double detail, + Window* view, + double which, + ExceptionState& exception_state) { + return MakeGarbageCollected<UIEvent>(context, type, detail, view, which, exception_state); +} + +UIEvent* UIEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<UIEventInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<UIEvent>(context, type, initializer->detail(), initializer->view(), initializer->which(), + exception_state); +} + +UIEvent::UIEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : Event(context, type) {} + +UIEvent::UIEvent(ExecutingContext* context, + const AtomicString& type, + double detail, + Window* view, + double which, + ExceptionState& exception_state) + : Event(context, type), detail_(detail), view_(view), which_(which) {} + +UIEvent::UIEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<UIEventInit>& initializer, + ExceptionState& exception_state) + : Event(context, type), + detail_(initializer->hasDetail() ? initializer->detail() : 0.0), + view_(initializer->hasView() ? initializer->view() : nullptr), + which_(initializer->hasWhich() ? initializer->which() : 0.0) {} + +UIEvent::UIEvent(ExecutingContext* context, const AtomicString& type, NativeUIEvent* native_ui_event) + : Event(context, type, &native_ui_event->native_event), + detail_(native_ui_event->detail), +#if ANDROID_32_BIT + view_(DynamicTo<Window>(BindingObject::From(reinterpret_cast<NativeBindingObject*>(native_ui_event->view)))), +#else + view_(DynamicTo<Window>(BindingObject::From(static_cast<NativeBindingObject*>(native_ui_event->view)))), +#endif + which_(native_ui_event->which) { +} + +double UIEvent::detail() const { + return detail_; +} + +Window* UIEvent::view() const { + return view_; +} + +double UIEvent::which() const { + return which_; +} + +bool UIEvent::IsUiEvent() const { + return true; +} + +void UIEvent::Trace(GCVisitor* visitor) const { + visitor->Trace(view_); + Event::Trace(visitor); +} + +} // namespace webf diff --git a/bridge/core/events/ui_event.d.ts b/bridge/core/events/ui_event.d.ts new file mode 100644 index 0000000000..ce831a16b7 --- /dev/null +++ b/bridge/core/events/ui_event.d.ts @@ -0,0 +1,16 @@ +import {Event} from "../dom/events/event"; +import {Window} from "../frame/window"; +import {UIEventInit} from "./ui_event_init"; + +/** Simple user interface events. */ +interface UIEvent extends Event { + // Returns a long with details about the event, depending on the event type. + // For click or dblclick events, UIEvent.detail is the current click count. + // For mousedown or mouseup events, UIEvent.detail is 1 plus the current click count. + // For all other UIEvent objects, UIEvent.detail is always zero. + readonly detail: number; + readonly view: Window | null; + /** @deprecated */ + readonly which: number; + new(type: string, init?: UIEventInit): UIEvent; +} \ No newline at end of file diff --git a/bridge/core/events/ui_event.h b/bridge/core/events/ui_event.h new file mode 100644 index 0000000000..e6e67f64a9 --- /dev/null +++ b/bridge/core/events/ui_event.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_EVENTS_UI_EVENT_H_ +#define BRIDGE_CORE_EVENTS_UI_EVENT_H_ + +#include "bindings/qjs/cppgc/member.h" +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/source_location.h" +#include "core/dom/events/event.h" +#include "core/frame/window.h" +#include "qjs_ui_event_init.h" + +namespace webf { + +struct NativeUIEvent; + +class UIEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = UIEvent*; + + static UIEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + static UIEvent* Create(ExecutingContext* context, + const AtomicString& type, + double detail, + Window* view, + double which, + ExceptionState& exception_state); + static UIEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<UIEventInit>& initializer, + ExceptionState& exception_state); + + explicit UIEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + + explicit UIEvent(ExecutingContext* context, + const AtomicString& type, + double detail, + Window* view, + double which, + ExceptionState& exception_state); + + explicit UIEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<UIEventInit>& initializer, + ExceptionState& exception_state); + + explicit UIEvent(ExecutingContext* context, const AtomicString& type, NativeUIEvent* native_ui_event); + + double detail() const; + Window* view() const; + double which() const; + + bool IsUiEvent() const override; + + void Trace(GCVisitor* visitor) const override; + + private: + double detail_; + Member<Window> view_; + double which_; +}; + +template <> +struct DowncastTraits<UIEvent> { + static bool AllowFrom(const Event& event) { return event.IsUiEvent(); } +}; + +} // namespace webf + +#endif // BRIDGE_CORE_EVENTS_UI_EVENT_H_ diff --git a/bridge/core/events/ui_event_init.d.ts b/bridge/core/events/ui_event_init.d.ts new file mode 100644 index 0000000000..376a2497fa --- /dev/null +++ b/bridge/core/events/ui_event_init.d.ts @@ -0,0 +1,13 @@ +import { EventInit } from "../dom/events/event_init"; +import {Window} from "../frame/window"; +import {UIEvent} from "./ui_event"; + +// @ts-ignore +@Dictionary() +export interface UIEventInit extends EventInit { + detail?: number; + view?: Window | null; + /** @deprecated */ + which?: number; + new(type: string, init?: UIEventInit): UIEvent; +} diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc new file mode 100644 index 0000000000..023a45e3b8 --- /dev/null +++ b/bridge/core/executing_context.cc @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "executing_context.h" + +#include <utility> +#include "bindings/qjs/converter_impl.h" +#include "built_in_string.h" +#include "core/dom/document.h" +#include "core/events/error_event.h" +#include "core/events/promise_rejection_event.h" +#include "event_type_names.h" +#include "foundation/logging.h" +#include "polyfill.h" +#include "qjs_window.h" +#include "timing/performance.h" + +namespace webf { + +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}; + +ExecutingContext::ExecutingContext(int32_t contextId, + JSExceptionHandler handler, + void* owner, + const uint64_t* dart_methods, + int32_t dart_methods_length) + : context_id_(contextId), + handler_(std::move(handler)), + owner_(owner), + unique_id_(context_unique_id++), + is_context_valid_(true), + dart_method_ptr_(std::make_unique<DartMethodPointer>(dart_methods, dart_methods_length)) { + // #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(); + + // Install performance + InstallPerformance(); + + //#if ENABLE_PROFILE + // nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); + // nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); + //#endif + + initWebFPolyFill(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() { + is_context_valid_ = false; + valid_contexts[context_id_] = false; + + // 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_); + + // Free active wrappers. + for (auto& active_wrapper : active_wrappers_) { + JS_FreeValue(ctx(), active_wrapper->ToQuickJSUnsafe()); + } +} + +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::IsContextValid() const { + return is_context_valid_; +} + +bool ExecutingContext::IsCtxValid() const { + return script_state_.Invalid(); +} + +void* ExecutingContext::owner() { + return owner_; +} + +bool ExecutingContext::HandleException(JSValue* exc) { + if (JS_IsException(*exc)) { + JSValue error = JS_GetException(script_state_.ctx()); + MemberMutationScope scope{this}; + 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); +} + +bool ExecutingContext::HandleException(ExceptionState& exception_state) { + if (exception_state.HasException()) { + JSValue error = JS_GetException(ctx()); + ReportError(error); + JS_FreeValue(ctx(), error); + return false; + } + return true; +} + +JSValue ExecutingContext::Global() { + return global_object_; +} + +JSContext* ExecutingContext::ctx() { + assert(IsCtxValid()); + return script_state_.ctx(); +} + +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) { + ExceptionState exceptionState; + + auto error_init = ErrorEventInit::Create(context->ctx(), error, exceptionState); + error_init->setError(Converter<IDLAny>::FromValue(context->ctx(), error, exceptionState)); + auto* error_event = ErrorEvent::Create(context, event_type_names::kerror, error_init, exceptionState); + + context->DispatchErrorEvent(error_event); +} + +static void DispatchPromiseRejectionEvent(const AtomicString& event_type, + ExecutingContext* context, + JSValueConst promise, + JSValueConst reason) { + ExceptionState exception_state; + + auto event_init = PromiseRejectionEventInit::Create(); + event_init->setPromise(Converter<IDLAny>::FromValue(context->ctx(), promise, exception_state)); + event_init->setReason(Converter<IDLAny>::FromValue(context->ctx(), reason, exception_state)); + auto event = PromiseRejectionEvent::Create(context, event_type, event_init, exception_state); + + context->window()->dispatchEvent(event, exception_state); + if (exception_state.HasException()) { + context->ReportError(reason); + } +} + +void ExecutingContext::FlushUICommand() { + if (!uiCommandBuffer()->empty()) { + dartMethodPtr()->flushUICommand(context_id_); + } +} + +void ExecutingContext::DispatchErrorEvent(ErrorEvent* error_event) { + if (in_dispatch_error_event_) { + return; + } + + DispatchErrorEventInterval(error_event); + ReportErrorEvent(error_event); +} + +void ExecutingContext::DispatchErrorEventInterval(ErrorEvent* error_event) { + assert(!in_dispatch_error_event_); + in_dispatch_error_event_ = true; + ExceptionState exception_state; + window_->dispatchEvent(error_event, exception_state); + in_dispatch_error_event_ = false; + + if (exception_state.HasException()) { + JSValue error = JS_GetException(ctx()); + ReportError(error); + JS_FreeValue(ctx(), error); + } +} + +void ExecutingContext::ReportErrorEvent(ErrorEvent* error_event) { + ReportError(error_event->error().QJSValue()); +} + +void ExecutingContext::DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, + JSValueConst promise, + JSValueConst reason) { + // Trigger unhandledRejection event. + DispatchPromiseRejectionEvent(event_type_names::kunhandledrejection, context, promise, reason); +} + +void ExecutingContext::DispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValue promise, JSValue error) { + // Trigger rejectionhandled event. + DispatchPromiseRejectionEvent(event_type_names::krejectionhandled, 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_; +} + +ModuleContextCoordinator* ExecutingContext::ModuleContexts() { + return &module_contexts_; +} + +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(); +} + +void ExecutingContext::InstallDocument() { + MemberMutationScope scope{this}; + document_ = MakeGarbageCollected<Document>(this); + DefineGlobalProperty("document", document_->ToQuickJS()); +} + +void ExecutingContext::InstallPerformance() { + MemberMutationScope scope{this}; + performance_ = MakeGarbageCollected<Performance>(this); + DefineGlobalProperty("performance", performance_->ToQuickJS()); +} + +void ExecutingContext::InstallGlobal() { + MemberMutationScope mutation_scope{this}; + window_ = MakeGarbageCollected<Window>(this); + JS_SetPrototype(ctx(), Global(), window_->ToQuickJSUnsafe()); + JS_SetOpaque(Global(), window_); +} + +void ExecutingContext::RegisterActiveScriptWrappers(ScriptWrappable* script_wrappable) { + active_wrappers_.emplace_back(script_wrappable); +} + +// An lock free context validator. +bool isContextValid(int32_t contextId) { + if (contextId > running_context_list) + return false; + return valid_contexts[contextId]; +} + +} // namespace webf diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h new file mode 100644 index 0000000000..da86d28ba2 --- /dev/null +++ b/bridge/core/executing_context.h @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_JS_CONTEXT_H +#define BRIDGE_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/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_context_coordinator.h" +#include "frame/module_listener_container.h" +#include "script_state.h" + +namespace webf { + +struct NativeByteCode { + uint8_t* bytes; + int32_t length; +}; + +class ExecutingContext; +class Document; +class Window; +class Performance; +class MemberMutationScope; +class ErrorEvent; +class ScriptWrappable; + +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 webf. +// Window : Document : ExecutionContext = 1 : 1 : 1 at any point in time. +class ExecutingContext { + public: + ExecutingContext() = delete; + ExecutingContext(int32_t contextId, + JSExceptionHandler handler, + void* owner, + const uint64_t* dart_methods, + int32_t dart_methods_length); + ~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 IsContextValid() const; + bool IsCtxValid() const; + JSValue Global(); + JSContext* ctx(); + FORCE_INLINE int32_t contextId() const { return context_id_; }; + FORCE_INLINE int32_t uniqueId() const { return unique_id_; } + void* owner(); + bool HandleException(JSValue* exc); + bool HandleException(ScriptValue* exc); + bool HandleException(ExceptionState& exception_state); + void ReportError(JSValueConst error); + void DrainPendingPromiseJobs(); + void DefineGlobalProperty(const char* prop, JSValueConst value); + 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(); + + // Register active script wrappers. + void RegisterActiveScriptWrappers(ScriptWrappable* script_wrappable); + + // 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 `webf.addModuleListener API`. + ModuleListenerContainer* ModuleListeners(); + + // Gets the ModuleCallbacks which from the 4th parameter of `webf.invokeModule` function. + ModuleContextCoordinator* ModuleContexts(); + + // 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() const { return document_; }; + FORCE_INLINE Window* window() const { return window_; } + FORCE_INLINE Performance* performance() const { return performance_; } + FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &ui_command_buffer_; }; + FORCE_INLINE const std::unique_ptr<DartMethodPointer>& dartMethodPtr() { return dart_method_ptr_; } + FORCE_INLINE std::chrono::time_point<std::chrono::system_clock> timeOrigin() const { return time_origin_; } + + // Force dart side to execute the pending ui commands. + void FlushUICommand(); + + void DispatchErrorEvent(ErrorEvent* error_event); + void DispatchErrorEventInterval(ErrorEvent* error_event); + void ReportErrorEvent(ErrorEvent* error_event); + + 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 webf 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(); + void InstallPerformance(); + + static void promiseRejectTracker(JSContext* ctx, + JSValueConst promise, + JSValueConst reason, + JS_BOOL is_handled, + void* opaque); + // Warning: Don't change the orders of members in ExecutingContext if you really know what are you doing. + // From C++ standard, https://isocpp.org/wiki/faq/dtors#order-dtors-for-members + // Members first initialized and destructed at the last. + // Dart methods ptr should keep alive when ExecutingContext is disposing. + const std::unique_ptr<DartMethodPointer> dart_method_ptr_ = nullptr; + // Keep uiCommandBuffer below dartMethod ptr to make sure we can flush all disposeEventTarget when UICommandBuffer + // release. + UICommandBuffer ui_command_buffer_{this}; + // Keep uiCommandBuffer above ScriptState to make sure we can collect all disposedEventTarget command when free + // JSContext. When call JSFreeContext(ctx) inside ScriptState, all eventTargets will be finalized and UICommandBuffer + // will be fill up to UICommand::disposeEventTarget commands. + // ---------------------------------------------------------------------- + // All members above ScriptState will be freed after ScriptState freed + // ---------------------------------------------------------------------- + ScriptState script_state_; + // ---------------------------------------------------------------------- + // All members below will be free before ScriptState freed. + // ---------------------------------------------------------------------- + bool is_context_valid_{false}; + int32_t context_id_; + JSExceptionHandler handler_; + void* owner_; + JSValue global_object_{JS_NULL}; + Document* document_{nullptr}; + Window* window_{nullptr}; + Performance* performance_{nullptr}; + DOMTimerCoordinator timers_; + ModuleListenerContainer module_listener_container_; + ModuleContextCoordinator module_contexts_; + ExecutionContextData context_data_{this}; + bool in_dispatch_error_event_{false}; + RejectedPromises rejected_promises_; + MemberMutationScope* active_mutation_scope{nullptr}; + std::vector<ScriptWrappable*> active_wrappers_; +}; + +class ObjectProperty { + WEBF_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}; +}; + +} // namespace webf + +#endif // BRIDGE_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..132ef743d1 --- /dev/null +++ b/bridge/core/executing_context_data.cc @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "executing_context_data.h" +#include "executing_context.h" + +namespace webf { + +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 webf diff --git a/bridge/core/executing_context_data.h b/bridge/core/executing_context_data.h new file mode 100644 index 0000000000..0ec9e24330 --- /dev/null +++ b/bridge/core/executing_context_data.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CONTEXT_DATA_H +#define BRIDGE_CONTEXT_DATA_H + +#include <quickjs/quickjs.h> +#include <unordered_map> +#include "bindings/qjs/wrapper_type_info.h" + +namespace webf { + +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 webf + +#endif // BRIDGE_CONTEXT_DATA_H diff --git a/bridge/bindings/qjs/js_context_test.cc b/bridge/core/executing_context_test.cc similarity index 81% rename from bridge/bindings/qjs/js_context_test.cc rename to bridge/core/executing_context_test.cc index d1b26c1a6d..e20dd9caf2 100644 --- a/bridge/bindings/qjs/js_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -7,9 +7,19 @@ #include "page.h" #include "webf_test_env.h" +using namespace webf; + TEST(Context, isValid) { - auto bridge = TEST_init(); - EXPECT_EQ(bridge->getContext()->isValid(), true); + { + auto bridge = TEST_init(); + EXPECT_EQ(bridge->GetExecutingContext()->IsContextValid(), true); + EXPECT_EQ(bridge->GetExecutingContext()->IsCtxValid(), true); + } + { + auto bridge = TEST_init(); + EXPECT_EQ(bridge->GetExecutingContext()->IsContextValid(), true); + EXPECT_EQ(bridge->GetExecutingContext()->IsCtxValid(), true); + } } TEST(Context, evalWithError) { @@ -70,12 +80,14 @@ TEST(Context, globalErrorHandlerTargetReturnToWindow) { webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; - EXPECT_STREQ(message.c_str(), "true"); + EXPECT_STREQ(message.c_str(), "error true true true"); }; std::string code = R"( -window.addEventListener('error', (e) => { console.log(e.target === window) }); -throw new Error('1234'); +let oldError = new Error('1234'); + +window.addEventListener('error', (e) => { console.log(e.type, e.target === window, window === globalThis, e.error === oldError) }); +throw oldError; )"; bridge->evaluateScript(code.c_str(), code.size(), "file://", 0); EXPECT_EQ(logCalled, true); @@ -95,7 +107,7 @@ 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[] = {"unhandled event {promise: Promise {...}, reason: Error {...}} true"}; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(logs[logIndex++].c_str(), message.c_str()); @@ -119,7 +131,7 @@ var p = new Promise(function (resolve, reject) { bridge->evaluateScript(code.c_str(), code.size(), "file://", 0); EXPECT_EQ(errorHandlerExecuted, true); EXPECT_EQ(logCalled, true); - EXPECT_EQ(logIndex, 2); + EXPECT_EQ(logIndex, 1); webf::WebFPage::consoleMessageHandler = nullptr; } @@ -160,11 +172,13 @@ generateRejectedPromise(true); } TEST(Context, unhandledRejectionEventWillTriggerWhenNotHandled) { - static bool errorHandlerExecuted = false; static bool logCalled = false; - auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; + auto errorHandler = [](int32_t contextId, const char* errmsg) {}; auto bridge = TEST_init(errorHandler); - webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "unhandledrejection fired: Error"); + }; std::string code = R"( window.addEventListener('unhandledrejection', event => { @@ -183,7 +197,6 @@ function generateRejectedPromise(isEventuallyHandled) { generateRejectedPromise(true); )"; bridge->evaluateScript(code.c_str(), code.size(), "file://", 0); - EXPECT_EQ(errorHandlerExecuted, false); EXPECT_EQ(logCalled, true); webf::WebFPage::consoleMessageHandler = nullptr; } @@ -223,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); webf::WebFPage::consoleMessageHandler = nullptr; @@ -243,7 +256,7 @@ TEST(Context, unrejectPromiseErrorWithMultipleContext) { }; auto bridge = TEST_init(errorHandler); - auto bridge2 = TEST_allocateNewPage(); + auto bridge2 = TEST_allocateNewPage(errorHandler); const char* code = " var p = new Promise(function (resolve, reject) {\n" " var nullObject = null;\n" @@ -255,27 +268,27 @@ TEST(Context, unrejectPromiseErrorWithMultipleContext) { bridge->evaluateScript(code, strlen(code), "file://", 0); bridge2->evaluateScript(code, strlen(code), "file://", 0); EXPECT_EQ(errorHandlerExecuted, true); - EXPECT_EQ(errorCalledCount, 4); + EXPECT_EQ(errorCalledCount, 2); } TEST(Context, accessGetUICommandItemsAfterDisposed) { int32_t contextId; { auto bridge = TEST_init(); - contextId = bridge->getContext()->getContextId(); + contextId = bridge->GetExecutingContext()->contextId(); } EXPECT_EQ(getUICommandItems(contextId), nullptr); } TEST(Context, disposeContext) { - initJSPagePool(1024 * 1024); - TEST_mockDartMethods(nullptr); + auto mockedDartMethods = TEST_getMockDartMethods(nullptr); + initJSPagePool(1024 * 1024, mockedDartMethods.data(), mockedDartMethods.size()); uint32_t contextId = 0; auto bridge = static_cast<webf::WebFPage*>(getPage(contextId)); static bool disposed = false; bridge->disposeCallback = [](webf::WebFPage* bridge) { disposed = true; }; - disposePage(bridge->getContext()->getContextId()); + disposePage(bridge->GetExecutingContext()->contextId()); EXPECT_EQ(disposed, true); } @@ -311,7 +324,9 @@ TEST(Context, windowInheritEventTarget) { WEBF_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); @@ -338,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 = webf::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); - EXPECT_EQ(nativeString->length, 10); + JSValue str = JS_NewString(bridge->GetExecutingContext()->ctx(), "helloworld"); + std::unique_ptr<webf::NativeString> nativeString = + webf::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 = webf::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); + JSValue str = JS_NewString(bridge->GetExecutingContext()->ctx(), "这是你的优乐美"); + std::unique_ptr<webf::NativeString> nativeString = + webf::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 = webf::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); + JSValue str = JS_NewString(bridge->GetExecutingContext()->ctx(), "……🤪"); + std::unique_ptr<webf::NativeString> nativeString = + webf::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..e5d1ee2253 --- /dev/null +++ b/bridge/core/fileapi/array_buffer_data.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ +#define BRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ + +namespace webf { + +struct ArrayBufferData { + uint8_t* buffer; + int32_t length; +}; + +} // namespace webf + +#endif // BRIDGE_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..7613e660d9 --- /dev/null +++ b/bridge/core/fileapi/blob.cc @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "blob.h" +#include <string> +#include "bindings/qjs/script_promise_resolver.h" +#include "built_in_string.h" +#include "core/executing_context.h" + +namespace webf { + +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() { + DidFinishLoading(); +} + +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(), 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(const 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 webf 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..4cdb9d5c6e --- /dev/null +++ b/bridge/core/fileapi/blob.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_BLOB_H +#define BRIDGE_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 webf { + +class Blob : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = Blob*; + 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, const 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(const std::vector<std::shared_ptr<BlobPart>>& data); + + private: + std::string mime_type_; + std::vector<uint8_t> _data; +}; + +} // namespace webf + +#endif // BRIDGE_BLOB_H diff --git a/bridge/core/fileapi/blob_part.cc b/bridge/core/fileapi/blob_part.cc new file mode 100644 index 0000000000..1c2426af79 --- /dev/null +++ b/bridge/core/fileapi/blob_part.cc @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "blob_part.h" +#include "qjs_blob.h" + +namespace webf { + +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); + auto blob_part = std::make_shared<BlobPart>(ctx, buffer, length, byte_offset, byte_length, byte_per_element); + JS_FreeValue(ctx, arrayBufferObject); + return blob_part; + } + + 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; + } + } + + 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 webf diff --git a/bridge/core/fileapi/blob_part.h b/bridge/core/fileapi/blob_part.h new file mode 100644 index 0000000000..8ad570d692 --- /dev/null +++ b/bridge/core/fileapi/blob_part.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_FILEAPI_BLOB_PART_H_ +#define BRIDGE_CORE_FILEAPI_BLOB_PART_H_ + +#include <quickjs/quickjs.h> +#include <memory> +#include <string> +#include <utility> +#include "bindings/qjs/exception_state.h" + +namespace webf { + +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 webf + +#endif // BRIDGE_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..76cfd92112 --- /dev/null +++ b/bridge/core/fileapi/blob_property_bag.cc @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "blob_property_bag.h" + +namespace webf { + +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 webf diff --git a/bridge/core/fileapi/blob_property_bag.h b/bridge/core/fileapi/blob_property_bag.h new file mode 100644 index 0000000000..8a7a82deb9 --- /dev/null +++ b/bridge/core/fileapi/blob_property_bag.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ +#define BRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ + +#include <quickjs/quickjs.h> +#include <memory> +#include "core/executing_context.h" + +namespace webf { + +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 webf + +#endif // BRIDGE_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..d6132e1f90 --- /dev/null +++ b/bridge/core/frame/console.cc @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "console.h" +#include <sstream> +#include "built_in_string.h" +#include "foundation/logging.h" + +namespace webf { + +void Console::__webf_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::__webf_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 webf diff --git a/bridge/core/frame/console.d.ts b/bridge/core/frame/console.d.ts new file mode 100644 index 0000000000..40b94bdb4a --- /dev/null +++ b/bridge/core/frame/console.d.ts @@ -0,0 +1 @@ +declare const __webf_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..da45d5c0ca --- /dev/null +++ b/bridge/core/frame/console.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#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 webf { + +class Console final { + public: + static void __webf_print__(ExecutingContext* context, + const AtomicString& log, + const AtomicString& level, + ExceptionState& exception); + static void __webf_print__(ExecutingContext* context, const AtomicString& log, ExceptionState& exception_state); +}; + +} // namespace webf + +#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 e20b14114c..f09d7d2459 100644 --- a/bridge/bindings/qjs/bom/console_test.cc +++ b/bridge/core/frame/console_test.cc @@ -5,10 +5,9 @@ #include "console.h" #include "gtest/gtest.h" -#include "page.h" #include "webf_test_env.h" -std::once_flag kGlobalClassIdFlag; +using namespace webf; 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..f457215f80 --- /dev/null +++ b/bridge/core/frame/dom_timer.cc @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#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 "webf_test_env.h" +#endif + +namespace webf { + +std::shared_ptr<DOMTimer> DOMTimer::create(ExecutingContext* context, + const std::shared_ptr<QJSFunction>& callback, + TimerKind timer_kind) { + return std::make_shared<DOMTimer>(context, callback, timer_kind); +} + +DOMTimer::DOMTimer(ExecutingContext* context, std::shared_ptr<QJSFunction> callback, TimerKind timer_kind) + : context_(context), callback_(std::move(callback)), status_(TimerStatus::kPending), kind_(timer_kind) {} + +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) { + timer_id_ = timerId; +} + +} // namespace webf diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h new file mode 100644 index 0000000000..25edf69766 --- /dev/null +++ b/bridge/core/frame/dom_timer.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_DOM_TIMER_H +#define BRIDGE_DOM_TIMER_H + +#include "bindings/qjs/qjs_function.h" +#include "bindings/qjs/script_wrappable.h" +#include "dom_timer_coordinator.h" + +namespace webf { + +class DOMTimer { + public: + enum TimerKind { kOnce, kMultiple }; + enum TimerStatus { kPending, kExecuting, kFinished, kCanceled }; + + static std::shared_ptr<DOMTimer> create(ExecutingContext* context, + const std::shared_ptr<QJSFunction>& callback, + TimerKind timer_kind); + DOMTimer(ExecutingContext* context, std::shared_ptr<QJSFunction> callback, TimerKind timer_kind); + + // Trigger timer callback. + void Fire(); + + TimerKind kind() const { return kind_; } + + [[nodiscard]] int32_t timerId() const { return timer_id_; }; + void setTimerId(int32_t timerId); + + void SetStatus(TimerStatus status) { status_ = status; } + [[nodiscard]] TimerStatus status() const { return status_; } + + ExecutingContext* context() { return context_; } + + private: + TimerKind kind_; + ExecutingContext* context_{nullptr}; + int32_t timer_id_{-1}; + TimerStatus status_; + std::shared_ptr<QJSFunction> callback_; +}; + +} // namespace webf + +#endif // BRIDGE_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..9540c54591 --- /dev/null +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "dom_timer_coordinator.h" +#include "core/dart_methods.h" +#include "core/executing_context.h" +#include "dom_timer.h" + +#if UNIT_TEST +#include "webf_test_env.h" +#endif + +namespace webf { + +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 context_id, const char* errmsg) { + auto* timer = static_cast<DOMTimer*>(ptr); + auto* context = timer->context(); + + if (!context->IsContextValid()) + return; + + handleTimerCallback(timer, errmsg); + + context->Timers()->removeTimeoutById(timer->timerId()); +} + +void DOMTimerCoordinator::installNewTimer(ExecutingContext* context, + int32_t timer_id, + std::shared_ptr<DOMTimer> timer) { + active_timers_[timer_id] = timer; +} + +void DOMTimerCoordinator::removeTimeoutById(int32_t timer_id) { + if (active_timers_.count(timer_id) == 0) + return; + auto timer = active_timers_[timer_id]; + assert(timer->status() == DOMTimer::TimerStatus::kFinished); + active_timers_.erase(timer_id); +} + +void DOMTimerCoordinator::forceStopTimeoutById(int32_t timer_id) { + if (active_timers_.count(timer_id) == 0) { + return; + } + auto timer = active_timers_[timer_id]; + timer->SetStatus(DOMTimer::TimerStatus::kCanceled); +} + +std::shared_ptr<DOMTimer> DOMTimerCoordinator::getTimerById(int32_t timer_id) { + if (active_timers_.count(timer_id) == 0) + return nullptr; + return active_timers_[timer_id]; +} + +} // namespace webf diff --git a/bridge/bindings/qjs/bom/dom_timer_coordinator.h b/bridge/core/frame/dom_timer_coordinator.h similarity index 60% rename from bridge/bindings/qjs/bom/dom_timer_coordinator.h rename to bridge/core/frame/dom_timer_coordinator.h index 6e48491331..a983b8f868 100644 --- a/bridge/bindings/qjs/bom/dom_timer_coordinator.h +++ b/bridge/core/frame/dom_timer_coordinator.h @@ -7,13 +7,14 @@ #define BRIDGE_BINDINGS_QJS_BOM_DOM_TIMER_COORDINATOR_H_ #include <quickjs/quickjs.h> +#include <memory> #include <unordered_map> #include <vector> -namespace webf::binding::qjs { +namespace webf { -class ExecutionContext; class DOMTimer; +class ExecutingContext; // Maintains a set of DOMTimers for a given page // DOMTimerCoordinator assigns IDs to timers; these IDs are @@ -23,20 +24,19 @@ 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 timer_id, 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); + // Then timer are going to be finished, remove them from active_timers_ list. + void removeTimeoutById(int32_t timer_id); + // Force stop and remove a timer, even if it's still executing. + void forceStopTimeoutById(int32_t timer_id); - void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func); + std::shared_ptr<DOMTimer> getTimerById(int32_t timer_id); private: - std::unordered_map<int, DOMTimer*> m_activeTimers; - std::vector<DOMTimer*> m_abandonedTimers; + std::unordered_map<int, std::shared_ptr<DOMTimer>> active_timers_; }; -} // namespace webf::binding::qjs +} // namespace webf #endif // BRIDGE_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 70% rename from bridge/bindings/qjs/bom/timer_test.cc rename to bridge/core/frame/dom_timer_test.cc index 4aa9550225..b7bcf2a682 100644 --- a/bridge/bindings/qjs/bom/timer_test.cc +++ b/bridge/core/frame/dom_timer_test.cc @@ -4,9 +4,9 @@ */ #include "gtest/gtest.h" -#include "page.h" #include "webf_bridge.h" #include "webf_test_env.h" +using namespace webf; TEST(Timer, setTimeout) { auto bridge = TEST_init(); @@ -37,14 +37,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; - webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + log_called = true; + }; std::string code = R"( function getCachedData() { @@ -63,6 +65,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(); + + webf::WebFPage::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..6cbb58900a --- /dev/null +++ b/bridge/core/frame/legacy/location.cc @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "location.h" +#include "core/executing_context.h" + +namespace webf { + +void Location::__webf_location_reload__(ExecutingContext* context, ExceptionState& exception_state) { + if (context->dartMethodPtr()->reloadApp == nullptr) { + exception_state.ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'reload': dart method (reloadApp) is not registered."); + return; + } + + context->FlushUICommand(); + context->dartMethodPtr()->reloadApp(context->contextId()); +} + +} // namespace webf diff --git a/bridge/core/frame/legacy/location.d.ts b/bridge/core/frame/legacy/location.d.ts new file mode 100644 index 0000000000..c80ee732fd --- /dev/null +++ b/bridge/core/frame/legacy/location.d.ts @@ -0,0 +1 @@ +declare const __webf_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..9f04b27444 --- /dev/null +++ b/bridge/core/frame/legacy/location.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_LOCATION_H +#define BRIDGE_LOCATION_H + +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/script_wrappable.h" + +namespace webf { + +class Location { + public: + static void __webf_location_reload__(ExecutingContext* context, ExceptionState& exception_state); +}; + +} // namespace webf + +#endif // BRIDGE_LOCATION_H diff --git a/bridge/core/frame/module_callback.cc b/bridge/core/frame/module_callback.cc new file mode 100644 index 0000000000..46380d7a77 --- /dev/null +++ b/bridge/core/frame/module_callback.cc @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "module_callback.h" + +namespace webf { + +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 webf diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h new file mode 100644 index 0000000000..4414e5a863 --- /dev/null +++ b/bridge/core/frame/module_callback.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_MODULE_CALLBACK_H +#define BRIDGE_MODULE_CALLBACK_H + +#include <quickjs/list.h> +#include "bindings/qjs/qjs_function.h" + +namespace webf { + +// ModuleCallback is an asynchronous callback function, usually from the 4th parameter of `webf.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 webf + +#endif // BRIDGE_MODULE_CALLBACK_H diff --git a/webf/ios/Assets/.gitkeep b/bridge/core/frame/module_callback_coordinator.cc similarity index 100% rename from webf/ios/Assets/.gitkeep rename to bridge/core/frame/module_callback_coordinator.cc diff --git a/bridge/core/frame/module_context_coordinator.cc b/bridge/core/frame/module_context_coordinator.cc new file mode 100644 index 0000000000..6d6611e5cb --- /dev/null +++ b/bridge/core/frame/module_context_coordinator.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "module_context_coordinator.h" + +namespace webf { + +void ModuleContextCoordinator::AddModuleContext(std::shared_ptr<ModuleContext> module_context) { + module_contexts_.push_front(std::move(module_context)); +} + +} // namespace webf diff --git a/bridge/core/frame/module_context_coordinator.h b/bridge/core/frame/module_context_coordinator.h new file mode 100644 index 0000000000..46533cad55 --- /dev/null +++ b/bridge/core/frame/module_context_coordinator.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_MODULE_CALLBACK_COORDINATOR_H +#define BRIDGE_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 webf { + +class ModuleListener; +class ModuleContext; + +class ModuleContextCoordinator final { + public: + void AddModuleContext(std::shared_ptr<ModuleContext> module_context); + + private: + std::forward_list<std::shared_ptr<ModuleContext>> module_contexts_; + friend ModuleListener; +}; + +} // namespace webf + +#endif // BRIDGE_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..7ac104e7a6 --- /dev/null +++ b/bridge/core/frame/module_listener.cc @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "module_listener.h" + +#include <utility> + +namespace webf { + +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)) {} + +const std::shared_ptr<QJSFunction>& ModuleListener::value() { + return function_; +} + +} // namespace webf diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h new file mode 100644 index 0000000000..d4e2afa15b --- /dev/null +++ b/bridge/core/frame/module_listener.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_MODULE_LISTENER_H +#define BRIDGE_MODULE_LISTENER_H + +#include "bindings/qjs/qjs_function.h" + +namespace webf { + +class ModuleContextCoordinator; +class ModuleListenerContainer; + +// ModuleListener is an persistent callback function. Registered from user with `webf.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); + + const std::shared_ptr<QJSFunction>& value(); + + private: + std::shared_ptr<QJSFunction> function_{nullptr}; + + friend ModuleListenerContainer; + friend ModuleContextCoordinator; +}; + +} // namespace webf + +#endif // BRIDGE_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..7d0a25eaf0 --- /dev/null +++ b/bridge/core/frame/module_listener_container.cc @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "module_listener_container.h" +#include "bindings/qjs/cppgc/gc_visitor.h" + +namespace webf { + +void ModuleListenerContainer::AddModuleListener(const AtomicString& name, + const std::shared_ptr<ModuleListener>& listener) { + listeners_[name] = listener; +} + +void ModuleListenerContainer::RemoveModuleListener(const AtomicString& name) { + listeners_.erase(name); +} + +std::shared_ptr<ModuleListener> ModuleListenerContainer::listener(const AtomicString& name) { + if (listeners_.count(name) == 0) + return nullptr; + return listeners_[name]; +} + +void ModuleListenerContainer::Clear() { + listeners_.clear(); +} + +} // namespace webf diff --git a/bridge/core/frame/module_listener_container.h b/bridge/core/frame/module_listener_container.h new file mode 100644 index 0000000000..a48c7c2c59 --- /dev/null +++ b/bridge/core/frame/module_listener_container.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_MODULE_LISTENER_CONTAINER_H +#define BRIDGE_MODULE_LISTENER_CONTAINER_H + +#include <unordered_map> +#include "module_listener.h" + +namespace webf { + +class ModuleListenerContainer final { + public: + void AddModuleListener(const AtomicString& name, const std::shared_ptr<ModuleListener>& listener); + void RemoveModuleListener(const AtomicString& name); + std::shared_ptr<ModuleListener> listener(const AtomicString& name); + void Clear(); + + private: + std::unordered_map<AtomicString, std::shared_ptr<ModuleListener>, AtomicString::KeyHasher> listeners_; + friend ModuleListener; +}; + +} // namespace webf + +#endif // BRIDGE_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..12eb0cc258 --- /dev/null +++ b/bridge/core/frame/module_manager.cc @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "module_manager.h" +#include "core/executing_context.h" +#include "foundation/logging.h" +#include "module_callback.h" + +namespace webf { + +struct ModuleContext { + ModuleContext(ExecutingContext* context, const std::shared_ptr<ModuleCallback>& callback) + : context(context), callback(callback) {} + ExecutingContext* context; + std::shared_ptr<ModuleCallback> callback; +}; + +NativeValue* handleInvokeModuleTransientCallback(void* ptr, + int32_t contextId, + const char* errmsg, + NativeValue* extra_data) { + auto* moduleContext = static_cast<ModuleContext*>(ptr); + ExecutingContext* context = moduleContext->context; + + if (!context->IsCtxValid() || !context->IsContextValid()) + return nullptr; + + if (moduleContext->callback == nullptr) { + JSValue exception = JS_ThrowTypeError(moduleContext->context->ctx(), + "Failed to execute '__webf_invoke_module__': callback is null."); + context->HandleException(&exception); + return nullptr; + } + + JSContext* ctx = moduleContext->context->ctx(); + + if (ctx == nullptr) + return nullptr; + + ExceptionState exception_state; + + NativeValue* return_value = nullptr; + if (errmsg != nullptr) { + ScriptValue error_object = ScriptValue::CreateErrorObject(ctx, errmsg); + ScriptValue arguments[] = {error_object}; + ScriptValue result = moduleContext->callback->value()->Invoke(ctx, ScriptValue::Empty(ctx), 1, arguments); + if (result.IsException()) { + context->HandleException(&result); + } + NativeValue native_result = result.ToNative(exception_state); + return_value = static_cast<NativeValue*>(malloc(sizeof(NativeValue))); + memcpy(return_value, &native_result, sizeof(NativeValue)); + } else { + ScriptValue arguments[] = {ScriptValue::Empty(ctx), ScriptValue(ctx, *extra_data)}; + ScriptValue result = moduleContext->callback->value()->Invoke(ctx, ScriptValue::Empty(ctx), 2, arguments); + if (result.IsException()) { + context->HandleException(&result); + } + NativeValue native_result = result.ToNative(exception_state); + return_value = static_cast<NativeValue*>(malloc(sizeof(NativeValue))); + memcpy(return_value, &native_result, sizeof(NativeValue)); + } + + if (exception_state.HasException()) { + context->HandleException(exception_state); + return nullptr; + } + + return return_value; +} + +NativeValue* handleInvokeModuleUnexpectedCallback(void* callbackContext, + int32_t contextId, + const char* errmsg, + NativeValue* extra_data) { + static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); + return nullptr; +} + +ScriptValue ModuleManager::__webf_invoke_module__(ExecutingContext* context, + const AtomicString& module_name, + const AtomicString& method, + ExceptionState& exception) { + ScriptValue empty = ScriptValue::Empty(context->ctx()); + return __webf_invoke_module__(context, module_name, method, empty, nullptr, exception); +} + +ScriptValue ModuleManager::__webf_invoke_module__(ExecutingContext* context, + const AtomicString& module_name, + const AtomicString& method, + ScriptValue& params_value, + ExceptionState& exception) { + return __webf_invoke_module__(context, module_name, method, params_value, nullptr, exception); +} + +ScriptValue ModuleManager::__webf_invoke_module__(ExecutingContext* context, + const AtomicString& module_name, + const AtomicString& method, + ScriptValue& params_value, + std::shared_ptr<QJSFunction> callback, + ExceptionState& exception) { + NativeValue params = params_value.ToNative(exception); + + if (exception.HasException()) { + return ScriptValue::Empty(context->ctx()); + } + + if (context->dartMethodPtr()->invokeModule == nullptr) { + exception.ThrowException( + context->ctx(), ErrorType::InternalError, + "Failed to execute '__webf_invoke_module__': dart method (invokeModule) is not registered."); + return ScriptValue::Empty(context->ctx()); + } + + NativeValue* result; + if (callback != nullptr) { + auto module_callback = ModuleCallback::Create(callback); + auto module_context = std::make_shared<ModuleContext>(context, module_callback); + context->ModuleContexts()->AddModuleContext(module_context); + result = context->dartMethodPtr()->invokeModule(module_context.get(), context->contextId(), + module_name.ToNativeString().get(), method.ToNativeString().get(), + ¶ms, handleInvokeModuleTransientCallback); + } else { + result = context->dartMethodPtr()->invokeModule(nullptr, context->contextId(), module_name.ToNativeString().get(), + method.ToNativeString().get(), ¶ms, + handleInvokeModuleUnexpectedCallback); + } + + if (result == nullptr) { + return ScriptValue::Empty(context->ctx()); + } + + return ScriptValue(context->ctx(), *result); +} + +void ModuleManager::__webf_add_module_listener__(ExecutingContext* context, + const AtomicString& module_name, + const std::shared_ptr<QJSFunction>& handler, + ExceptionState& exception) { + auto listener = ModuleListener::Create(handler); + context->ModuleListeners()->AddModuleListener(module_name, listener); +} + +void ModuleManager::__webf_remove_module_listener__(ExecutingContext* context, + const AtomicString& module_name, + ExceptionState& exception_state) { + context->ModuleListeners()->RemoveModuleListener(module_name); +} + +void ModuleManager::__webf_clear_module_listener__(ExecutingContext* context, ExceptionState& exception_state) { + context->ModuleListeners()->Clear(); +} + +} // namespace webf diff --git a/bridge/core/frame/module_manager.d.ts b/bridge/core/frame/module_manager.d.ts new file mode 100644 index 0000000000..c9e568a3c7 --- /dev/null +++ b/bridge/core/frame/module_manager.d.ts @@ -0,0 +1,4 @@ +declare const __webf_invoke_module__: (moduleName: string, methodName: string, paramsValue?: any, callback?: Function) => any; +declare const __webf_add_module_listener__: (moduleName: string, callback: Function) => void; +declare const __webf_remove_module_listener__: (moduleName: string) => void; +declare const __webf_clear_module_listener__: () => void; diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h new file mode 100644 index 0000000000..76c37efe95 --- /dev/null +++ b/bridge/core/frame/module_manager.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_MODULE_MANAGER_H +#define BRIDGE_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 webf { + +class ModuleManager { + public: + static ScriptValue __webf_invoke_module__(ExecutingContext* context, + const AtomicString& module_name, + const AtomicString& method, + ExceptionState& exception); + static ScriptValue __webf_invoke_module__(ExecutingContext* context, + const AtomicString& module_name, + const AtomicString& method, + ScriptValue& params_value, + ExceptionState& exception); + static ScriptValue __webf_invoke_module__(ExecutingContext* context, + const AtomicString& module_name, + const AtomicString& method, + ScriptValue& params_value, + std::shared_ptr<QJSFunction> callback, + ExceptionState& exception); + static void __webf_add_module_listener__(ExecutingContext* context, + const AtomicString& module_name, + const std::shared_ptr<QJSFunction>& handler, + ExceptionState& exception); + static void __webf_remove_module_listener__(ExecutingContext* context, + const AtomicString& module_name, + ExceptionState& exception_state); + static void __webf_clear_module_listener__(ExecutingContext* context, ExceptionState& exception_state); +}; + +} // namespace webf + +#endif // BRIDGE_MODULE_MANAGER_H diff --git a/bridge/bindings/qjs/module_manager_test.cc b/bridge/core/frame/module_manager_test.cc similarity index 64% rename from bridge/bindings/qjs/module_manager_test.cc rename to bridge/core/frame/module_manager_test.cc index adc3d252d5..3e3ca463b2 100644 --- a/bridge/bindings/qjs/module_manager_test.cc +++ b/bridge/core/frame/module_manager_test.cc @@ -4,12 +4,34 @@ */ #include <gtest/gtest.h> -#include "executing_context.h" -#include "host_object.h" -#include "page.h" #include "webf_test_env.h" -namespace webf::binding::qjs { +namespace webf { + +TEST(ModuleManager, ShouldReturnCorrectValue) { + bool static errorCalled = false; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + webf::WebFPage::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 = webf.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; @@ -20,7 +42,7 @@ TEST(ModuleManager, shouldThrowErrorWhenBadJSON) { }); webf::WebFPage::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 = { @@ -35,7 +57,7 @@ let object = { object.other = object; webf.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); } @@ -52,7 +74,7 @@ TEST(ModuleManager, invokeModuleError) { "'}"); }; - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); std::string code = std::string(R"( function f() { @@ -66,9 +88,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 webf::binding::qjs +} // namespace webf diff --git a/bridge/core/frame/screen.cc b/bridge/core/frame/screen.cc new file mode 100644 index 0000000000..e02ee7f93e --- /dev/null +++ b/bridge/core/frame/screen.cc @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "screen.h" +#include "core/frame/window.h" +#include "foundation/native_value_converter.h" + +namespace webf { + +Screen::Screen(Window* window, NativeBindingObject* native_binding_object) + : EventTargetWithInlineData(window->GetExecutingContext(), native_binding_object) {} + +} // namespace webf diff --git a/bridge/core/frame/screen.d.ts b/bridge/core/frame/screen.d.ts new file mode 100644 index 0000000000..bdf763dba5 --- /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<int64>; + 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..569058d4a9 --- /dev/null +++ b/bridge/core/frame/screen.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_SCREEN_H +#define BRIDGE_SCREEN_H + +#include "core/dom/events/event_target.h" + +namespace webf { + +class Window; + +struct NativeScreen {}; + +class Screen : public EventTargetWithInlineData { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = Screen*; + explicit Screen(Window* window, NativeBindingObject* binding_object); + + private: +}; + +} // namespace webf + +#endif // BRIDGE_SCREEN_H diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc new file mode 100644 index 0000000000..960599f1bc --- /dev/null +++ b/bridge/core/frame/window.cc @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF 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 "core/executing_context.h" +#include "event_type_names.h" +#include "foundation/native_value_converter.h" + +namespace webf { + +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), + }; + InvokeBindingMethod(binding_call_methods::kopen, 1, args, exception_state); + return this; +} + +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->hasLeft() ? options->left() : 0.0), + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->hasTop() ? options->top() : 0.0), + }; + 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->hasLeft() ? options->left() : 0.0), + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->hasTop() ? options->top() : 0.0), + }; + 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); +} + +bool Window::IsWindowOrWorkerGlobalScope() const { + return true; +} + +void Window::Trace(GCVisitor* visitor) const { + visitor->Trace(screen_); + EventTargetWithInlineData::Trace(visitor); +} + +JSValue Window::ToQuickJS() const { + return JS_GetGlobalObject(ctx()); +} + +} // namespace webf diff --git a/bridge/core/frame/window.d.ts b/bridge/core/frame/window.d.ts new file mode 100644 index 0000000000..3bc8dcd85d --- /dev/null +++ b/bridge/core/frame/window.d.ts @@ -0,0 +1,36 @@ +import {EventTarget} from "../dom/events/event_target"; +import {ScrollOptions} from "../dom/scroll_options"; +import {ScrollToOptions} from "../dom/scroll_to_options"; +import {Screen} from "./screen"; +import {WindowEventHandlers} from "./window_event_handlers"; +import {GlobalEventHandlers} from "../dom/global_event_handlers"; + +interface Window extends EventTarget, WindowEventHandlers, GlobalEventHandlers { + open(url?: string): Window | null; + scroll(x: number, y: number): void; + scroll(options?: ScrollToOptions): void; + 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..fe9ed1429c --- /dev/null +++ b/bridge/core/frame/window.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_WINDOW_H +#define BRIDGE_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 webf { + +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); + + bool IsWindowOrWorkerGlobalScope() const override; + + void Trace(GCVisitor* visitor) const override; + + // Override default ToQuickJS() to return Global object when access `window` property. + JSValue ToQuickJS() const override; + + private: + Member<Screen> screen_; +}; + +template <> +struct DowncastTraits<Window> { + static bool AllowFrom(const EventTarget& event_target) { return event_target.IsWindowOrWorkerGlobalScope(); } + static bool AllowFrom(const BindingObject& binding_object) { + return binding_object.IsEventTarget() && DynamicTo<EventTarget>(binding_object)->IsWindowOrWorkerGlobalScope(); + } +}; + +} // namespace webf + +#endif // BRIDGE_WINDOW_H diff --git a/bridge/core/frame/window_event_handlers.d.ts b/bridge/core/frame/window_event_handlers.d.ts new file mode 100644 index 0000000000..35bc1d1247 --- /dev/null +++ b/bridge/core/frame/window_event_handlers.d.ts @@ -0,0 +1,16 @@ +type IDLEventHandler = Function; + +// @ts-ignore +@Mixin() +export interface WindowEventHandlers { + onbeforeunload: IDLEventHandler | null; + onhashchange: IDLEventHandler | null; + onmessage: IDLEventHandler | null; + onmessageerror: IDLEventHandler | null; + onpagehide: IDLEventHandler | null; + onpageshow: IDLEventHandler | null; + onpopstate: IDLEventHandler | null; + onrejectionhandled: IDLEventHandler | null; + onunhandledrejection: IDLEventHandler | null; + onunload: IDLEventHandler | null; +} \ No newline at end of file diff --git a/bridge/core/frame/window_event_handlers.h b/bridge/core/frame/window_event_handlers.h new file mode 100644 index 0000000000..bf3db3dd65 --- /dev/null +++ b/bridge/core/frame/window_event_handlers.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_FRAME_WINDOW_EVENT_HANDLERS_H_ +#define BRIDGE_CORE_FRAME_WINDOW_EVENT_HANDLERS_H_ + +#include "core/dom/document.h" +#include "event_type_names.h" +#include "foundation/macros.h" + +namespace webf { + +class WindowEventHandlers { + WEBF_STATIC_ONLY(WindowEventHandlers); + + public: + DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(beforeunload, kbeforeunload); + DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(hashchange, khashchange); + DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(message, kmessage); + DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(messageerror, kmessageerror); + DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(pagehide, kpagehide); + DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(pageshow, kpageshow); + DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(popstate, kpopstate); + DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(rejectionhandled, krejectionhandled); + DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(unhandledrejection, kunhandledrejection); + DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(unload, kunload); +}; + +} // namespace webf + +#endif // BRIDGE_CORE_FRAME_WINDOW_EVENT_HANDLERS_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..b6a0962ddc --- /dev/null +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "window_or_worker_global_scope.h" +#include "core/frame/dom_timer.h" + +namespace webf { + +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); + context->Timers()->forceStopTimeoutById(timer->timerId()); + return; + } + + if (context->Timers()->getTimerById(timer->timerId()) == nullptr) + return; + + // Trigger timer callbacks. + timer->Fire(); +} + +static void handleTransientCallback(void* ptr, int32_t contextId, const char* errmsg) { + auto* timer = static_cast<DOMTimer*>(ptr); + auto* context = timer->context(); + + if (!context->IsContextValid()) + return; + + if (timer->status() == DOMTimer::TimerStatus::kCanceled) { + 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->IsContextValid()) + return; + + if (timer->status() == DOMTimer::TimerStatus::kCanceled) { + 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, DOMTimer::TimerKind::kOnce); + 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, DOMTimer::TimerKind::kMultiple); + + 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()->forceStopTimeoutById(timerId); +} + +void WindowOrWorkerGlobalScope::clearInterval(ExecutingContext* context, int32_t timerId, ExceptionState& exception) { + if (context->dartMethodPtr()->clearTimeout == nullptr) { + exception.ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'clearTimeout': dart method (clearTimeout) is not registered."); + return; + } + + context->dartMethodPtr()->clearTimeout(context->contextId(), timerId); + context->Timers()->forceStopTimeoutById(timerId); +} + +} // namespace webf 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..caa8e1f371 --- /dev/null +++ b/bridge/core/frame/window_or_worker_global_scope.d.ts @@ -0,0 +1,13 @@ +// @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; + +// @ts-ignore +declare const clearInterval: (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..df2d3897c2 --- /dev/null +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H +#define BRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H + +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/qjs_function.h" +#include "core/executing_context.h" + +namespace webf { + +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); + static void clearInterval(ExecutingContext* context, int32_t timerId, ExceptionState& exception); +}; + +} // namespace webf + +#endif // BRIDGE_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 57% rename from bridge/bindings/qjs/bom/window_test.cc rename to bridge/core/frame/window_test.cc index a3d3d7d707..73febd8b30 100644 --- a/bridge/bindings/qjs/bom/window_test.cc +++ b/bridge/core/frame/window_test.cc @@ -5,9 +5,29 @@ #include "window.h" #include "gtest/gtest.h" -#include "page.h" #include "webf_test_env.h" +using namespace webf; + +TEST(Window, windowIsGlobalThis) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::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) { + WEBF_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; bool static logCalled = false; @@ -19,7 +39,7 @@ TEST(Window, instanceofEventTarget) { WEBF_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); @@ -29,8 +49,12 @@ TEST(Window, instanceofEventTarget) { TEST(Window, requestAnimationFrame) { auto bridge = TEST_init(); + bool static logCalled = false; - webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { EXPECT_STREQ(message.c_str(), "456"); }; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "456"); + logCalled = true; + }; std::string code = R"( requestAnimationFrame(() => { @@ -39,7 +63,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) { @@ -48,14 +74,14 @@ TEST(Window, cancelAnimationFrame) { webf::WebFPage::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) { @@ -64,14 +90,14 @@ TEST(Window, postMessage) { static bool logCalled = false; webf::WebFPage::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 }, '*'); )"); @@ -87,12 +113,27 @@ TEST(Window, location) { static bool logCalled = false; webf::WebFPage::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); } + +TEST(Window, onloadShouldExist) { + static bool errorCalled = false; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + + std::string code = std::string(R"( + console.assert('onload' in window); + )"); + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + EXPECT_EQ(errorCalled, false); +} diff --git a/bridge/core/html/canvas/canvas_rendering_context.cc b/bridge/core/html/canvas/canvas_rendering_context.cc new file mode 100644 index 0000000000..87627687bc --- /dev/null +++ b/bridge/core/html/canvas/canvas_rendering_context.cc @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "canvas_rendering_context.h" +#include "core/executing_context.h" + +namespace webf { + +CanvasRenderingContext::CanvasRenderingContext(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} + +bool CanvasRenderingContext::IsCanvas2d() const { + return false; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/canvas/canvas_rendering_context.d.ts b/bridge/core/html/canvas/canvas_rendering_context.d.ts new file mode 100644 index 0000000000..6e7f1fa68d --- /dev/null +++ b/bridge/core/html/canvas/canvas_rendering_context.d.ts @@ -0,0 +1,5 @@ +// @ts-ignore +interface CanvasRenderingContext { + new(): void; +} + diff --git a/bridge/core/html/canvas/canvas_rendering_context.h b/bridge/core/html/canvas/canvas_rendering_context.h new file mode 100644 index 0000000000..44340fd2da --- /dev/null +++ b/bridge/core/html/canvas/canvas_rendering_context.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_HTML_CANVAS_CANVAS_RENDERING_CONTEXT_H_ +#define BRIDGE_CORE_HTML_CANVAS_CANVAS_RENDERING_CONTEXT_H_ + +#include "bindings/qjs/script_wrappable.h" +#include "core/binding_object.h" + +namespace webf { + +class CanvasRenderingContext : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = CanvasRenderingContext*; + explicit CanvasRenderingContext(ExecutingContext* context); + + virtual bool IsCanvas2d() const; + + private: +}; + +} // namespace webf + +#endif // BRIDGE_CORE_HTML_CANVAS_CANVAS_RENDERING_CONTEXT_H_ diff --git a/bridge/core/html/canvas/canvas_rendering_context_2d.cc b/bridge/core/html/canvas/canvas_rendering_context_2d.cc new file mode 100644 index 0000000000..83e22b4837 --- /dev/null +++ b/bridge/core/html/canvas/canvas_rendering_context_2d.cc @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "canvas_rendering_context_2d.h" + +namespace webf { + +bool CanvasRenderingContext2D::IsCanvas2d() const { + return true; +} + +CanvasRenderingContext2D::CanvasRenderingContext2D(ExecutingContext* context, + NativeBindingObject* native_binding_object) + : BindingObject(context, native_binding_object), CanvasRenderingContext(context) {} + +NativeValue CanvasRenderingContext2D::HandleCallFromDartSide(const NativeValue* method, + int32_t argc, + const NativeValue* argv) { + return Native_NewNull(); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/canvas/canvas_rendering_context_2d.d.ts b/bridge/core/html/canvas/canvas_rendering_context_2d.d.ts new file mode 100644 index 0000000000..6243187ab6 --- /dev/null +++ b/bridge/core/html/canvas/canvas_rendering_context_2d.d.ts @@ -0,0 +1,48 @@ +import {HTMLImageElement} from "../html_image_element"; + +interface CanvasRenderingContext2D extends CanvasRenderingContext { + fillStyle: DartImpl<string>; + direction: DartImpl<string>; + font: DartImpl<string>; + strokeStyle: DartImpl<string>; + lineCap: DartImpl<string>; + lineDashOffset: DartImpl<double>; + lineJoin: DartImpl<string>; + lineWidth: DartImpl<double>; + miterLimit: DartImpl<double>; + textAlign: DartImpl<string>; + textBaseline: DartImpl<string>; + // @TODO: Following number should be double. + // Reference https://html.spec.whatwg.org/multipage/canvas.html + arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, anticlockwise?: boolean): DartImpl<void>; + arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): DartImpl<void>; + beginPath(): DartImpl<void>; + bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): DartImpl<void>; + clearRect(x: number, y: number, w: number, h: number): DartImpl<void>; + closePath(): DartImpl<void>; + clip(path?: string): DartImpl<void>; + drawImage(image: HTMLImageElement, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): DartImpl<void>; + drawImage(image: HTMLImageElement, dx: number, dy: number, dw: number, dh: number): DartImpl<void>; + drawImage(image: HTMLImageElement, dx: number, dy: number): DartImpl<void>; + ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise?: boolean): DartImpl<void>; + fill(path?: string): DartImpl<void>; + fillRect(x: number, y: number, w: number, h: number): DartImpl<void>; + fillText(text: string, x: number, y: number, maxWidth?: number): DartImpl<void>; + lineTo(x: number, y: number): DartImpl<void>; + moveTo(x: number, y: number): DartImpl<void>; + rect(x: number, y: number, w: number, h: number): DartImpl<void>; + restore(): DartImpl<void>; + resetTransform(): DartImpl<void>; + rotate(angle: number): DartImpl<void>; + quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): DartImpl<void>; + stroke(): DartImpl<void>; + strokeRect(x: number, y: number, w: number, h: number): DartImpl<void>; + save(): DartImpl<void>; + scale(x: number, y: number): DartImpl<void>; + strokeText(text: string, x: number, y: number, maxWidth?: number): DartImpl<void>; + setTransform(a: number, b: number, c: number, d: number, e: number, f: number): DartImpl<void>; + transform(a: number, b: number, c: number, d: number, e: number, f: number): DartImpl<void>; + translate(x: number, y: number): DartImpl<void>; + reset(): DartImpl<void>; + new(): void; +} diff --git a/bridge/core/html/canvas/canvas_rendering_context_2d.h b/bridge/core/html/canvas/canvas_rendering_context_2d.h new file mode 100644 index 0000000000..4dc8947225 --- /dev/null +++ b/bridge/core/html/canvas/canvas_rendering_context_2d.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_HTML_CANVAS_CANVAS_RENDERING_CONTEXT_2D_H_ +#define BRIDGE_CORE_HTML_CANVAS_CANVAS_RENDERING_CONTEXT_2D_H_ + +#include "canvas_rendering_context.h" +#include "core/html/html_image_element.h" + +namespace webf { + +class CanvasRenderingContext2D : public CanvasRenderingContext, public BindingObject { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = CanvasRenderingContext2D*; + CanvasRenderingContext2D() = delete; + explicit CanvasRenderingContext2D(ExecutingContext* context, NativeBindingObject* native_binding_object); + + NativeValue HandleCallFromDartSide(const NativeValue* method, int32_t argc, const NativeValue* argv) override; + + bool IsCanvas2d() const override; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_HTML_CANVAS_CANVAS_RENDERING_CONTEXT_2D_H_ 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..fda87609fb --- /dev/null +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "html_canvas_element.h" +#include "binding_call_methods.h" +#include "canvas_rendering_context_2d.h" +#include "canvas_types.h" +#include "foundation/native_value_converter.h" +#include "html_names.h" +#include "qjs_html_canvas_element.h" + +namespace webf { + +HTMLCanvasElement::HTMLCanvasElement(Document& document) : HTMLElement(html_names::kcanvas, &document) {} + +CanvasRenderingContext* HTMLCanvasElement::getContext(const AtomicString& type, ExceptionState& exception_state) const { + NativeValue arguments[] = {NativeValueConverter<NativeTypeString>::ToNativeValue(type)}; + NativeValue value = InvokeBindingMethod(binding_call_methods::kgetContext, 1, arguments, exception_state); + NativeBindingObject* native_binding_object = + NativeValueConverter<NativeTypePointer<NativeBindingObject>>::FromNativeValue(value); + + if (type == canvas_types::k2d) { + return MakeGarbageCollected<CanvasRenderingContext2D>(GetExecutingContext(), native_binding_object); + } + + return nullptr; +} + +bool HTMLCanvasElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLCanvasElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/canvas/html_canvas_element.d.ts b/bridge/core/html/canvas/html_canvas_element.d.ts new file mode 100644 index 0000000000..33cc2adf7c --- /dev/null +++ b/bridge/core/html/canvas/html_canvas_element.d.ts @@ -0,0 +1,8 @@ +import {HTMLElement} from "../html_element"; + +interface HTMLCanvasElement extends HTMLElement { + width: DartImpl<int64>; + height: DartImpl<int64>; + getContext(contextType: string): CanvasRenderingContext | null; + new(): void; +} 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..32210e2ed1 --- /dev/null +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ +#define BRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ + +#include "canvas_rendering_context.h" +#include "core/html/html_element.h" + +namespace webf { + +class HTMLCanvasElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLCanvasElement(Document&); + + CanvasRenderingContext* getContext(const AtomicString& type, ExceptionState& exception_state) const; + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ diff --git a/bridge/core/html/canvas_types.json5 b/bridge/core/html/canvas_types.json5 new file mode 100644 index 0000000000..816ae33817 --- /dev/null +++ b/bridge/core/html/canvas_types.json5 @@ -0,0 +1,13 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "canvas_types" + } + ] + }, + "data": [ + "2d" + ] +} diff --git a/bridge/core/html/collection_type.h b/bridge/core/html/collection_type.h new file mode 100644 index 0000000000..bbf1d8c8e7 --- /dev/null +++ b/bridge/core/html/collection_type.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights + * reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#ifndef BRIDGE_CORE_HTML_COLLECTION_TYPE_H_ +#define BRIDGE_CORE_HTML_COLLECTION_TYPE_H_ + +namespace webf { + +enum CollectionType { + // Unnamed HTMLCollection types cached in the document. + kDocImages, // all <img> elements in the document + kDocApplets, // all <object> and <applet> elements + kDocEmbeds, // all <embed> elements + kDocForms, // all <form> elements + kDocLinks, // all <a> _and_ <area> elements with a value for href + kDocAnchors, // all <a> elements with a value for name + kDocScripts, // all <script> elements + kDocAll, // "all" elements (IE) + + // Unnamed HTMLCollection types cached in elements. + kNodeChildren, // first-level children (ParentNode DOM interface) + kTableTBodies, // all <tbody> elements in this table + kTSectionRows, // all row elements in this table section + kTableRows, + kTRCells, // all cells in this row + kSelectOptions, + kSelectedOptions, + kDataListOptions, + kMapAreas, + kFormControls, + + // Named HTMLCollection types cached in the document. + kWindowNamedItems, + kDocumentNamedItems, + kDocumentAllNamedItems, + + // Named HTMLCollection types cached in elements. + kClassCollectionType, + kTagCollectionType, + kHTMLTagCollectionType, + kTagCollectionNSType, + + // Live NodeList. + kNameNodeListType, + kRadioNodeListType, + kRadioImgNodeListType, + kLabelsNodeListType, +}; + +static const CollectionType kFirstNamedCollectionType = kWindowNamedItems; +static const CollectionType kFirstLiveNodeListType = kNameNodeListType; + +inline bool IsUnnamedHTMLCollectionType(CollectionType type) { + return type < kFirstNamedCollectionType; +} + +inline bool IsHTMLCollectionType(CollectionType type) { + return type < kFirstLiveNodeListType; +} + +inline bool IsLiveNodeListType(CollectionType type) { + return type >= kFirstLiveNodeListType; +} + +} // namespace webf + +#endif // BRIDGE_CORE_HTML_COLLECTION_TYPE_H_ diff --git a/bridge/core/html/custom/widget_element.cc b/bridge/core/html/custom/widget_element.cc new file mode 100644 index 0000000000..a08744c7b4 --- /dev/null +++ b/bridge/core/html/custom/widget_element.cc @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "widget_element.h" +#include "core/dom/document.h" +#include "foundation/native_value_converter.h" + +namespace webf { + +WidgetElement::WidgetElement(const AtomicString& tag_name, Document* document) + : HTMLElement(tag_name, document, ConstructionType::kCreateWidgetElement) {} + +bool WidgetElement::IsValidName(const AtomicString& name) { + assert(Document::IsValidName(name)); + StringView string_view = name.ToStringView(); + + const char* string = string_view.Characters8(); + for (int i = 0; i < string_view.length(); i++) { + if (string[i] == '-') + return true; + } + + return false; +} + +bool WidgetElement::IsUnderScoreProperty(const AtomicString& name) { + StringView string_view = name.ToStringView(); + + const char* string = string_view.Characters8(); + return string_view.length() > 0 && string[0] == '_'; +} + +bool WidgetElement::NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state) { + NativeValue result = GetBindingProperty(key, exception_state); + return result.tag != NativeTag::TAG_NULL; +} + +void WidgetElement::NamedPropertyEnumerator(std::vector<AtomicString>& names, ExceptionState& exception_state) { + NativeValue result = GetAllBindingPropertyNames(exception_state); + assert(result.tag == NativeTag::TAG_LIST); + std::vector<AtomicString> property_names = + NativeValueConverter<NativeTypeArray<NativeTypeString>>::FromNativeValue(ctx(), result); + names.reserve(property_names.size()); + for (auto& property_name : property_names) { + names.emplace_back(property_name); + } +} + +ScriptValue WidgetElement::item(const AtomicString& key, ExceptionState& exception_state) { + // Properties with underscore are taken as raw javascript property. + if (IsUnderScoreProperty(key)) { + if (unimplemented_properties_.count(key) > 0) { + return unimplemented_properties_[key]; + } + + return ScriptValue::Empty(ctx()); + } + + return ScriptValue(ctx(), GetBindingProperty(key, exception_state)); +} + +bool WidgetElement::SetItem(const AtomicString& key, const ScriptValue& value, ExceptionState& exception_state) { + if (IsUnderScoreProperty(key)) { + unimplemented_properties_[key] = value; + return true; + } + + NativeValue result = SetBindingProperty(key, value.ToNative(exception_state), exception_state); + return NativeValueConverter<NativeTypeBool>::FromNativeValue(result); +} + +bool WidgetElement::IsWidgetElement() const { + return true; +} + +void WidgetElement::Trace(GCVisitor* visitor) const { + HTMLElement::Trace(visitor); + for (auto& entry : unimplemented_properties_) { + entry.second.Trace(visitor); + } +} + +void WidgetElement::CloneNonAttributePropertiesFrom(const Element& other, CloneChildrenFlag flag) { + auto* other_widget_element = DynamicTo<WidgetElement>(other); + if (other_widget_element) { + unimplemented_properties_ = other_widget_element->unimplemented_properties_; + } +} + +bool WidgetElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return true; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/custom/widget_element.d.ts b/bridge/core/html/custom/widget_element.d.ts new file mode 100644 index 0000000000..5a3b454bc1 --- /dev/null +++ b/bridge/core/html/custom/widget_element.d.ts @@ -0,0 +1,6 @@ +import {HTMLElement} from "../html_element"; + +interface WidgetElement extends HTMLElement { + [key: string]: any; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/html/custom/widget_element.h b/bridge/core/html/custom/widget_element.h new file mode 100644 index 0000000000..f240240703 --- /dev/null +++ b/bridge/core/html/custom/widget_element.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_DOM_WIDGET_ELEMENT_H_ +#define WEBF_CORE_DOM_WIDGET_ELEMENT_H_ + +#include <unordered_map> +#include "core/html/html_element.h" + +namespace webf { + +// All properties and methods from WidgetElement are defined in Dart side. +// +// There must be a corresponding Dart WidgetElement class implements the properties and methods with this element. +// The WidgetElement class in C++ is a wrapper and proxy all operations to the dart side. +class WidgetElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = WidgetElement*; + WidgetElement(const AtomicString& tag_name, Document* document); + + static bool IsValidName(const AtomicString& name); + static bool IsUnderScoreProperty(const AtomicString& name); + + bool NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state); + void NamedPropertyEnumerator(std::vector<AtomicString>& names, ExceptionState&); + + ScriptValue item(const AtomicString& key, ExceptionState& exception_state); + bool SetItem(const AtomicString& key, const ScriptValue& value, ExceptionState& exception_state); + + bool IsWidgetElement() const override; + + void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) override; + + void Trace(GCVisitor* visitor) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + private: + std::unordered_map<AtomicString, ScriptValue, AtomicString::KeyHasher> unimplemented_properties_; +}; + +template <> +struct DowncastTraits<WidgetElement> { + static bool AllowFrom(const Element& element) { return element.IsWidgetElement(); } +}; + +} // namespace webf + +#endif // WEBF_CORE_DOM_WIDGET_ELEMENT_H_ diff --git a/bridge/core/html/custom/widget_element_test.cc b/bridge/core/html/custom/widget_element_test.cc new file mode 100644 index 0000000000..5f820e7816 --- /dev/null +++ b/bridge/core/html/custom/widget_element_test.cc @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "widget_element.h" +#include "gtest/gtest.h" +#include "webf_test_env.h" + +using namespace webf; + +TEST(WidgetElement, setPropertyEventHandler) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "1111"); + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let checkbox = document.createElement('flutter-checkbox'); " + "function f(){ console.log(1111); }; " + "checkbox.onclick = f; " + "checkbox.dispatchEvent(new Event('click'));"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} diff --git a/bridge/core/html/forms/html_button_element.cc b/bridge/core/html/forms/html_button_element.cc new file mode 100644 index 0000000000..198e6895f4 --- /dev/null +++ b/bridge/core/html/forms/html_button_element.cc @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "html_button_element.h" +#include "qjs_html_button_element.h" + +namespace webf { + +bool HTMLButtonElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLButtonElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/forms/html_button_element.d.ts b/bridge/core/html/forms/html_button_element.d.ts new file mode 100644 index 0000000000..a554d458aa --- /dev/null +++ b/bridge/core/html/forms/html_button_element.d.ts @@ -0,0 +1,9 @@ +import {HTMLElement} from "../html_element"; + +interface HTMLButtonElement extends HTMLElement { + disabled: DartImpl<boolean>; + type: DartImpl<string>; + name: DartImpl<string>; + value: DartImpl<string>; + new(): void; +} 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..8b4bc65083 --- /dev/null +++ b/bridge/core/html/forms/html_button_element.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ +#define BRIDGE_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ + +#include "core/html/html_element.h" + +namespace webf { + +class HTMLButtonElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + private: +}; + +} // namespace webf + +#endif // BRIDGE_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..83406b96f3 --- /dev/null +++ b/bridge/core/html/forms/html_input_element.cc @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "html_input_element.h" +#include "html_names.h" +#include "qjs_html_input_element.h" + +namespace webf { + +HTMLInputElement::HTMLInputElement(Document& document) : HTMLElement(html_names::kinput, &document) {} + +bool HTMLInputElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLInputElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/forms/html_input_element.d.ts b/bridge/core/html/forms/html_input_element.d.ts new file mode 100644 index 0000000000..a082ffff92 --- /dev/null +++ b/bridge/core/html/forms/html_input_element.d.ts @@ -0,0 +1,30 @@ +import {HTMLElement} from "../html_element"; + +interface HTMLInputElement extends HTMLElement { + width: DartImpl<number>; + height: DartImpl<number>; + defaultValue: DartImpl<string>; + value: DartImpl<string>; + accept: DartImpl<string>; + autocomplete: DartImpl<string>; + autofocus: DartImpl<boolean>; + checked: DartImpl<boolean>; + disabled: DartImpl<boolean>; + min: DartImpl<string>; + max: DartImpl<string>; + minLength: DartImpl<double>; + maxLength: DartImpl<double>; + size: DartImpl<double>; + multiple: DartImpl<boolean>; + name: DartImpl<string>; + step: DartImpl<string>; + pattern: DartImpl<string>; + required: DartImpl<boolean>; + readonly: DartImpl<boolean>; + placeholder: DartImpl<string> + type: DartImpl<string>; + inputMode: DartImpl<string>; + focus(): DartImpl<void>; + blur(): DartImpl<void>; + new(): void; +} 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..835f436f22 --- /dev/null +++ b/bridge/core/html/forms/html_input_element.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ +#define BRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ + +#include "core/html/html_element.h" + +namespace webf { + +class HTMLInputElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLInputElement(Document&); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; +}; + +} // namespace webf + +#endif // BRIDGE_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..b620d57f5b --- /dev/null +++ b/bridge/core/html/forms/html_textarea_element.cc @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "html_textarea_element.h" +#include "html_names.h" +#include "qjs_html_textarea_element.h" + +namespace webf { + +HTMLTextareaElement::HTMLTextareaElement(Document& document) : HTMLElement(html_names::ktextarea, &document) {} + +bool HTMLTextareaElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLTextareaElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/forms/html_textarea_element.d.ts b/bridge/core/html/forms/html_textarea_element.d.ts new file mode 100644 index 0000000000..5862f0e3bf --- /dev/null +++ b/bridge/core/html/forms/html_textarea_element.d.ts @@ -0,0 +1,23 @@ +// https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element +import {HTMLElement} from "../html_element"; + +interface HTMLTextareaElement extends HTMLElement { + defaultValue: DartImpl<string>; + value: DartImpl<string>; + cols: DartImpl<double>; + rows: DartImpl<double>; + wrap: DartImpl<string>; + autofocus: DartImpl<boolean>; + autocomplete: DartImpl<string>; + disabled: DartImpl<boolean>; + minLength: DartImpl<double>; + maxLength: DartImpl<double>; + name: DartImpl<string>; + placeholder: DartImpl<string>; + readonly: DartImpl<boolean>; + required: DartImpl<boolean>; + inputMode: DartImpl<string>; + focus(): DartImpl<void>; + blur(): DartImpl<void>; + new(): 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..91469be3e1 --- /dev/null +++ b/bridge/core/html/forms/html_textarea_element.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ +#define BRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ + +#include "core/html/html_element.h" + +namespace webf { + +class HTMLTextareaElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLTextareaElement(Document&); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; +}; + +} // namespace webf + +#endif // BRIDGE_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..ab487898c4 --- /dev/null +++ b/bridge/core/html/html_all_collection.cc @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "html_all_collection.h" + +namespace webf { + +HTMLAllCollection::HTMLAllCollection(ContainerNode* base, CollectionType type) : HTMLCollection(base, type) {} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/html_all_collection.d.ts b/bridge/core/html/html_all_collection.d.ts new file mode 100644 index 0000000000..b480adba11 --- /dev/null +++ b/bridge/core/html/html_all_collection.d.ts @@ -0,0 +1,8 @@ +import {Element} from "../dom/element"; + +interface HTMLAllCollection { + readonly length: double; + item(index: double): Element | null; + readonly [key: number]: Element | null; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/html/html_all_collection.h b/bridge/core/html/html_all_collection.h new file mode 100644 index 0000000000..4615ad1b4a --- /dev/null +++ b/bridge/core/html/html_all_collection.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_HTML_HTML_ALL_COLLECTION_H_ +#define BRIDGE_CORE_HTML_HTML_ALL_COLLECTION_H_ + +#include "legacy/html_collection.h" + +namespace webf { + +class ContainerNode; + +class HTMLAllCollection : public HTMLCollection { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = HTMLAllCollection*; + HTMLAllCollection(ContainerNode* base, CollectionType); + + private: +}; + +} // namespace webf + +#endif // BRIDGE_CORE_HTML_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..8507159498 --- /dev/null +++ b/bridge/core/html/html_anchor_element.cc @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "html_anchor_element.h" +#include "html_names.h" +#include "qjs_html_anchor_element.h" + +namespace webf { + +HTMLAnchorElement::HTMLAnchorElement(Document& document) : HTMLElement(html_names::ka, &document) {} + +bool HTMLAnchorElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLAnchorElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/html_anchor_element.d.ts b/bridge/core/html/html_anchor_element.d.ts new file mode 100644 index 0000000000..fa3f09e8ba --- /dev/null +++ b/bridge/core/html/html_anchor_element.d.ts @@ -0,0 +1,23 @@ +import {Element} from "../dom/element"; + +interface HTMLAnchorElement extends Element { + target: DartImpl<string>; + accessKey: DartImpl<string>; + download: DartImpl<string>; + ping: DartImpl<string>; + rel: DartImpl<string>; + type: DartImpl<string>; + text: DartImpl<string>; + href: DartImpl<string>; + readonly origin: DartImpl<string>; + protocol: DartImpl<string>; + username: DartImpl<string>; + password: DartImpl<string>; + host: DartImpl<string>; + hostname: DartImpl<string>; + port: DartImpl<string>; + pathname: DartImpl<string>; + search: DartImpl<string>; + hash: DartImpl<string>; + new(): void; +} diff --git a/bridge/core/html/html_anchor_element.h b/bridge/core/html/html_anchor_element.h new file mode 100644 index 0000000000..9401092f08 --- /dev/null +++ b/bridge/core/html/html_anchor_element.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_HTML_ANCHOR_ELEMENT_H +#define BRIDGE_HTML_ANCHOR_ELEMENT_H + +#include "html_element.h" + +namespace webf { + +class HTMLAnchorElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLAnchorElement(Document& document); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + private: +}; + +} // namespace webf + +#endif // BRIDGE_HTML_ANCHOR_ELEMENT_H diff --git a/bridge/core/html/html_attribute_names.json5 b/bridge/core/html/html_attribute_names.json5 new file mode 100644 index 0000000000..4942fcc685 --- /dev/null +++ b/bridge/core/html/html_attribute_names.json5 @@ -0,0 +1,348 @@ +{ + metadata: { + templates: [] + }, + data: [ + "abbr", + "accept-charset", + "accept", + "accesskey", + "action", + "align", + "alink", + "allow", + "allowfullscreen", + "allowpaymentrequest", + "alt", + "anchor", + "anonymous", + "archive", + "as", + "async", + "attributionsrc", + "autocapitalize", + "autocomplete", + "autocorrect", + "autofocus", + "autoplay", + "autopictureinpicture", + "axis", + "background", + "behavior", + "bgcolor", + "blocking", + "border", + "bordercolor", + "capture", + "cellpadding", + "cellspacing", + "char", + "challenge", + "charoff", + "charset", + "checked", + "cite", + "class", + "classid", + "clear", + "code", + "codebase", + "codetype", + "color", + "cols", + "colspan", + "compact", + "content", + "contenteditable", + "controls", + "controlslist", + "coords", + "crossorigin", + "csp", + "data", + "datetime", + "declare", + "decoding", + "default", + "defer", + "delegatesfocus", + "dir", + "direction", + "dirname", + "disabled", + "disablepictureinpicture", + "disableremoteplayback", + "download", + "draggable", + "elementtiming", + "enctype", + "end", + "enterkeyhint", + "event", + "exportparts", + "face", + "fetchpriority", + "focusgroup", + "for", + "form", + "formaction", + "formenctype", + "formmethod", + "formnovalidate", + "formtarget", + "frame", + "frameborder", + "headers", + "height", + "hidden", + "high", + "href", + "hreflang", + "hreftranslate", + "hspace", + "http-equiv", + "id", + "imagesizes", + "imagesrcset", + "incremental", + "inert", + "defaultopen", + "inputmode", + "integrity", + "is", + "ismap", + "itemprop", + "keytype", + "kind", + "invisible", + "label", + "lang", + "language", + "latencyhint", + "leftmargin", + "link", + "list", + "loading", + "longdesc", + "loop", + "low", + "lowsrc", + "manifest", + "marginheight", + "marginwidth", + "max", + "maxlength", + "mayscript", + "media", + "method", + "min", + "minlength", + "mode", + "multiple", + "muted", + "name", + "nohref", + "nomodule", + "nonce", + "noresize", + "noshade", + "novalidate", + "nowrap", + "object", + "onabort", + "onafterprint", + "onanimationstart", + "onanimationiteration", + "onanimationend", + "onauxclick", + "onbeforecopy", + "onbeforecut", + "onbeforeinput", + "onbeforepaste", + "onbeforeprint", + "onbeforeunload", + "onblur", + "oncancel", + "oncanplay", + "oncanplaythrough", + "onchange", + "onclick", + "onclose", + "oncontentvisibilityautostatechanged", + "oncontextlost", + "oncontextmenu", + "oncontextrestored", + "oncopy", + "oncuechange", + "oncut", + "ondblclick", + "ondrag", + "ondragend", + "ondragenter", + "ondragleave", + "ondragover", + "ondragstart", + "ondrop", + "ondurationchange", + "onemptied", + "onended", + "onerror", + "onfocus", + "onfocusin", + "onfocusout", + "onformdata", + "ongotpointercapture", + "onhashchange", + "oninput", + "oninvalid", + "onkeydown", + "onkeypress", + "onkeyup", + "onlanguagechange", + "onload", + "onloadeddata", + "onloadedmetadata", + "onloadstart", + "onlostpointercapture", + "onmessage", + "onmessageerror", + "onmousedown", + "onmouseenter", + "onmouseleave", + "onmousemove", + "onmouseout", + "onmouseover", + "onmouseup", + "onmousewheel", + "ononline", + "onoffline", + "onorientationchange", + "onoverscroll", + "onpagehide", + "onpageshow", + "onpaste", + "onpause", + "onplay", + "onplaying", + "onpointercancel", + "onpointerdown", + "onpointerenter", + "onpointerleave", + "onpointermove", + "onpointerout", + "onpointerover", + "onpointerrawupdate", + "onpointerup", + "onpopstate", + "onportalactivate", + "onprogress", + "onratechange", + "onreset", + "onresize", + "onscroll", + "onscrollend", + "onsearch", + "onsecuritypolicyviolation", + "onseeked", + "onseeking", + "onselect", + "onselectstart", + "onselectionchange", + "onshow", + "onslotchange", + "onstalled", + "onstorage", + "onsuspend", + "onsubmit", + "ontimeupdate", + "ontimezonechange", + "ontoggle", + "ontouchstart", + "ontouchmove", + "ontouchend", + "ontouchcancel", + "ontransitionend", + "onunload", + "onvolumechange", + "onwaiting", + "onwebkitanimationstart", + "onwebkitanimationiteration", + "onwebkitanimationend", + "onwebkitfullscreenchange", + "onwebkitfullscreenerror", + "onwebkittransitionend", + "onwheel", + "open", + "optimum", + "part", + "pattern", + "placeholder", + "playsinline", + "ping", + "policy", + "popup", + "popuphidetarget", + "popuphovertarget", + "popupshowtarget", + "popuptoggletarget", + "poster", + "preload", + "property", + "pseudo", + "readonly", + "referrerpolicy", + "rel", + "required", + "rev", + "reversed", + "role", + "rows", + "rowspan", + "rules", + "sandbox", + "scheme", + "scope", + "scrollamount", + "scrolldelay", + "scrolling", + "select", + "selected", + "shadowroot", + "shadowrootdelegatesfocus", + "shape", + "size", + "sizes", + "slot", + "span", + "spellcheck", + "src", + "srcset", + "srcdoc", + "srclang", + "standby", + "start", + "step", + "style", + "summary", + "tabindex", + "target", + "text", + "title", + "topmargin", + "translate", + "truespeed", + "trusttoken", + "type", + "usemap", + "valign", + "value", + "valuetype", + "version", + "vlink", + "vspace", + "virtualkeyboardpolicy", + "webkitdirectory", + "width", + "wrap", + ], +} \ No newline at end of file diff --git a/bridge/core/html/html_body_element.cc b/bridge/core/html/html_body_element.cc new file mode 100644 index 0000000000..bd5d5dd962 --- /dev/null +++ b/bridge/core/html/html_body_element.cc @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "html_body_element.h" +#include "html_names.h" +#include "qjs_html_body_element.h" + +namespace webf { + +HTMLBodyElement::HTMLBodyElement(Document& document) : HTMLElement(html_names::kbody, &document) {} + +bool HTMLBodyElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLBodyElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/html_body_element.d.ts b/bridge/core/html/html_body_element.d.ts new file mode 100644 index 0000000000..fd04e56800 --- /dev/null +++ b/bridge/core/html/html_body_element.d.ts @@ -0,0 +1,13 @@ +import {HTMLElement} from "./html_element"; +import {IDLEventHandler, WindowEventHandlers} from "../frame/window_event_handlers"; + +export interface HTMLBodyElement extends HTMLElement, WindowEventHandlers { + onblur: IDLEventHandler | null; + onerror: IDLEventHandler | null; + onfocus: IDLEventHandler | null; + onload: IDLEventHandler | null; + onresize: IDLEventHandler | null; + onscroll: IDLEventHandler | null; + + 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..23589f4375 --- /dev/null +++ b/bridge/core/html/html_body_element.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ +#define BRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ + +#include "core/dom/document.h" +#include "core/frame/window_event_handlers.h" +#include "html_element.h" + +namespace webf { + +class HTMLBodyElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = HTMLBodyElement*; + explicit HTMLBodyElement(Document&); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(blur, kblur); + DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(error, kerror); + DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(focus, kfocus); + DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(load, kload); + DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(resize, kresize); + DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(scroll, kscroll); + DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(orientationchange, korientationchange); +}; + +} // namespace webf + +#endif // BRIDGE_CORE_HTML_HTML_BODY_ELEMENT_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..0f473324f7 --- /dev/null +++ b/bridge/core/html/html_div_element.cc @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "html_div_element.h" +#include "html_names.h" +#include "qjs_html_div_element.h" + +namespace webf { + +HTMLDivElement::HTMLDivElement(Document& document) : HTMLElement(html_names::kdiv, &document) {} + +bool HTMLDivElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLDivElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/html_div_element.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..be766ad08d --- /dev/null +++ b/bridge/core/html/html_div_element.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ +#define BRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ + +#include "html_element.h" + +namespace webf { + +class HTMLDivElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = HTMLDivElement*; + explicit HTMLDivElement(Document&); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + private: +}; + +} // namespace webf + +#endif // BRIDGE_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..3f87b9b830 --- /dev/null +++ b/bridge/core/html/html_element.cc @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "html_element.h" +#include "qjs_html_element.h" + +namespace webf { + +bool HTMLElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLElement::IsAttributeDefinedInternal(key) || Element::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/html_element.d.ts b/bridge/core/html/html_element.d.ts new file mode 100644 index 0000000000..3e4ba38de0 --- /dev/null +++ b/bridge/core/html/html_element.d.ts @@ -0,0 +1,15 @@ +import {Element} from "../dom/element"; +import {GlobalEventHandlers} from "../dom/global_event_handlers"; + +export interface HTMLElement extends Element, GlobalEventHandlers { + // CSSOM View Module + // https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface + readonly offsetTop: DartImpl<double>; + readonly offsetLeft: DartImpl<double>; + readonly offsetWidth: DartImpl<double>; + readonly offsetHeight: DartImpl<double>; + + click(): DartImpl<void>; + + new(): void; +} diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h new file mode 100644 index 0000000000..6b08186b63 --- /dev/null +++ b/bridge/core/html/html_element.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_HTML_HTML_ELEMENT_H_ +#define BRIDGE_CORE_HTML_HTML_ELEMENT_H_ + +#include "core/dom/element.h" +#include "core/dom/global_event_handlers.h" + +namespace webf { + +class HTMLElement : public Element { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = HTMLElement*; + HTMLElement(const AtomicString& tag_name, Document* document, ConstructionType); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + 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 webf + +#endif // BRIDGE_CORE_HTML_HTML_ELEMENT_H_ diff --git a/bridge/bindings/qjs/dom/text_node_test.cc b/bridge/core/html/html_element_test.cc similarity index 60% rename from bridge/bindings/qjs/dom/text_node_test.cc rename to bridge/core/html/html_element_test.cc index 8fe76b30a3..70147ea996 100644 --- a/bridge/bindings/qjs/dom/text_node_test.cc +++ b/bridge/core/html/html_element_test.cc @@ -1,30 +1,29 @@ /* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#include "event_target.h" +#include "html_element.h" #include "gtest/gtest.h" -#include "page.h" #include "webf_test_env.h" -TEST(TextNode, instanceofNode) { +using namespace webf; + +TEST(HTMLElement, globalEventHandlerRegistered) { bool static errorCalled = false; bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "1234"); logCalled = true; - EXPECT_STREQ(message.c_str(), "true true"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { WEBF_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);"; + "let div = document.createElement('div'); function f(){ console.log(1234); }; div.onclick = f; " + "div.dispatchEvent(new Event('click'));"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); } diff --git a/bridge/core/html/html_head_element.cc b/bridge/core/html/html_head_element.cc new file mode 100644 index 0000000000..83bdb095a4 --- /dev/null +++ b/bridge/core/html/html_head_element.cc @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "html_head_element.h" +#include "html_names.h" +#include "qjs_html_head_element.h" + +namespace webf { + +HTMLHeadElement::HTMLHeadElement(Document& document) : HTMLElement(html_names::khead, &document) {} + +bool HTMLHeadElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLHeadElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/html_head_element.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..6213266a8d --- /dev/null +++ b/bridge/core/html/html_head_element.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ +#define BRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ + +#include "html_element.h" + +namespace webf { + +class HTMLHeadElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = HTMLHeadElement*; + explicit HTMLHeadElement(Document&); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + private: +}; + +} // namespace webf + +#endif // BRIDGE_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..a89fd91076 --- /dev/null +++ b/bridge/core/html/html_html_element.cc @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "html_html_element.h" +#include "html_names.h" +#include "qjs_html_html_element.h" + +namespace webf { + +HTMLHtmlElement::HTMLHtmlElement(Document& document) : HTMLElement(html_names::khtml, &document) {} + +bool HTMLHtmlElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLHtmlElement::IsAttributeDefinedInternal(key) || HTMLHtmlElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/html_html_element.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..475106e2c1 --- /dev/null +++ b/bridge/core/html/html_html_element.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ +#define BRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ + +#include "html_element.h" + +namespace webf { + +class HTMLHtmlElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = HTMLHtmlElement*; + explicit HTMLHtmlElement(Document&); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + private: +}; + +} // namespace webf + +#endif // BRIDGE_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..565c6059b3 --- /dev/null +++ b/bridge/core/html/html_image_element.cc @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "html_image_element.h" +#include "html_names.h" +#include "qjs_html_image_element.h" + +namespace webf { + +HTMLImageElement::HTMLImageElement(Document& document) : HTMLElement(html_names::kimg, &document) {} + +bool HTMLImageElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLImageElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +ScriptPromise HTMLImageElement::decode(ExceptionState& exception_state) const { + exception_state.ThrowException(ctx(), ErrorType::InternalError, "Not implemented."); + // @TODO not implemented. + return ScriptPromise(); +} + +bool HTMLImageElement::KeepAlive() const { + return true; +} + +} // namespace webf diff --git a/bridge/core/html/html_image_element.d.ts b/bridge/core/html/html_image_element.d.ts new file mode 100644 index 0000000000..9ab275e70b --- /dev/null +++ b/bridge/core/html/html_image_element.d.ts @@ -0,0 +1,20 @@ +import {HTMLElement} from "./html_element"; + +interface HTMLImageElement extends HTMLElement { + alt: DartImpl<string>; + src: DartImpl<string>; + srcset: DartImpl<string>; + sizes: DartImpl<string>; + width: DartImpl<int64>; + height: DartImpl<int64>; + readonly naturalWidth: DartImpl<int64>; + readonly naturalHeight: DartImpl<int64>; + readonly complete: DartImpl<boolean>; + readonly currentSrc: DartImpl<boolean>; + decoding: DartImpl<string>; + fetchPriority: DartImpl<string>; + loading: DartImpl<string>; + + decode(): Promise<void>; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h new file mode 100644 index 0000000000..5b85deb40f --- /dev/null +++ b/bridge/core/html/html_image_element.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ +#define BRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ + +#include "html_element.h" + +namespace webf { + +class HTMLImageElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = HTMLImageElement*; + explicit HTMLImageElement(Document& document); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + bool KeepAlive() const override; + + ScriptPromise decode(ExceptionState& exception_state) const; + + private: +}; + +} // namespace webf + +#endif // BRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ diff --git a/bridge/core/html/html_link_element.cc b/bridge/core/html/html_link_element.cc new file mode 100644 index 0000000000..6459ca87ed --- /dev/null +++ b/bridge/core/html/html_link_element.cc @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "html_link_element.h" +#include "html_names.h" +#include "qjs_html_link_element.h" + +namespace webf { + +HTMLLinkElement::HTMLLinkElement(Document& document) : HTMLElement(html_names::klink, &document) {} + +bool HTMLLinkElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLLinkElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/html_link_element.d.ts b/bridge/core/html/html_link_element.d.ts new file mode 100644 index 0000000000..3f1f73e739 --- /dev/null +++ b/bridge/core/html/html_link_element.d.ts @@ -0,0 +1,9 @@ +import {HTMLElement} from "./html_element"; + +interface HTMLLinkElement extends HTMLElement { + disabled: DartImpl<boolean>; + rel: DartImpl<string>; + href: DartImpl<string>; + type: DartImpl<string>; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/html/html_link_element.h b/bridge/core/html/html_link_element.h new file mode 100644 index 0000000000..04800fdb82 --- /dev/null +++ b/bridge/core/html/html_link_element.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_HTML_HTML_LINK_ELEMENT_H_ +#define WEBF_CORE_HTML_HTML_LINK_ELEMENT_H_ + +#include "html_element.h" + +namespace webf { + +class HTMLLinkElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLLinkElement(Document& document); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + private: +}; + +} // namespace webf + +#endif // WEBF_CORE_HTML_HTML_LINK_ELEMENT_H_ diff --git a/bridge/core/html/html_script_element.cc b/bridge/core/html/html_script_element.cc new file mode 100644 index 0000000000..42a564f412 --- /dev/null +++ b/bridge/core/html/html_script_element.cc @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "html_script_element.h" +#include "html_names.h" +#include "qjs_html_script_element.h" +#include "script_type_names.h" + +namespace webf { + +HTMLScriptElement::HTMLScriptElement(Document& document) : HTMLElement(html_names::kscript, &document) {} + +bool HTMLScriptElement::supports(const AtomicString& type, ExceptionState& exception_state) { + // Only class module support now. + if (type == script_type_names::kclassic) { + return true; + } + return false; +} + +bool HTMLScriptElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLScriptElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/html_script_element.d.ts b/bridge/core/html/html_script_element.d.ts new file mode 100644 index 0000000000..94214ebd28 --- /dev/null +++ b/bridge/core/html/html_script_element.d.ts @@ -0,0 +1,11 @@ +import {HTMLElement} from "./html_element"; + +interface HTMLScriptElement extends HTMLElement { + src: DartImpl<string>; + type: DartImpl<string>; + noModule: DartImpl<boolean>; + async: DartImpl<boolean>; + text: DartImpl<string>; + supports(type: string): StaticMember<boolean>; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/html/html_script_element.h b/bridge/core/html/html_script_element.h new file mode 100644 index 0000000000..c02142cb69 --- /dev/null +++ b/bridge/core/html/html_script_element.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ +#define BRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ + +#include "html_element.h" + +namespace webf { + +class HTMLScriptElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + static bool supports(const AtomicString& type, ExceptionState& exception_state); + + explicit HTMLScriptElement(Document& document); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + private: +}; + +} // namespace webf + +#endif // BRIDGE_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..9134cad299 --- /dev/null +++ b/bridge/core/html/html_tag_names.json5 @@ -0,0 +1,57 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "html_names", + "deps": [ + "./html_attribute_names.json5" + ] + }, + { + "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": "link", + "interfaceName": "HTMLLinkElement", + "filename": "html_link_element" + }, + { + "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..e00cea764b --- /dev/null +++ b/bridge/core/html/html_template_element.cc @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "html_template_element.h" +#include "core/dom/document_fragment.h" +#include "html_names.h" +#include "qjs_html_template_element.h" + +namespace webf { + +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(); +} + +bool HTMLTemplateElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLTemplateElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/html_template_element.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..da93a6b275 --- /dev/null +++ b/bridge/core/html/html_template_element.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_HTML_TEMPLATE_ELEMENT_H +#define BRIDGE_HTML_TEMPLATE_ELEMENT_H + +#include "html_element.h" + +namespace webf { + +class DocumentFragment; + +class HTMLTemplateElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLTemplateElement(Document& document); + + DocumentFragment* content() const; + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + private: + DocumentFragment* ContentInternal() const; + mutable Member<DocumentFragment> content_; +}; + +} // namespace webf + +#endif // BRIDGE_TEMPLATE_ELEMENTT_H diff --git a/bridge/core/html/html_unknown_element.cc b/bridge/core/html/html_unknown_element.cc new file mode 100644 index 0000000000..f378590723 --- /dev/null +++ b/bridge/core/html/html_unknown_element.cc @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "html_unknown_element.h" +#include "qjs_html_unknown_element.h" + +namespace webf { + +HTMLUnknownElement::HTMLUnknownElement(const AtomicString& tag_name, Document& document) + : HTMLElement(tag_name, &document) {} + +bool HTMLUnknownElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLUnknownElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/html_unknown_element.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..3ead0006d1 --- /dev/null +++ b/bridge/core/html/html_unknown_element.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ +#define BRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ + +#include "core/html/html_element.h" + +namespace webf { + +class HTMLUnknownElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLUnknownElement(const AtomicString&, Document& document); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + private: +}; + +} // namespace webf + +#endif // BRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ diff --git a/bridge/core/html/image.cc b/bridge/core/html/image.cc new file mode 100644 index 0000000000..385ae3fd3a --- /dev/null +++ b/bridge/core/html/image.cc @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "image.h" +#include "qjs_image.h" + +namespace webf { + +Image* Image::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected<Image>(context, exception_state); +} + +Image::Image(ExecutingContext* context, ExceptionState& exception_state) : HTMLImageElement(*context->document()) {} + +bool Image::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSImage::IsAttributeDefinedInternal(key) || HTMLImageElement::IsAttributeDefinedInternal(key); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/image.d.ts b/bridge/core/html/image.d.ts new file mode 100644 index 0000000000..471fcced59 --- /dev/null +++ b/bridge/core/html/image.d.ts @@ -0,0 +1,5 @@ +import {HTMLImageElement} from "./html_image_element"; + +interface Image extends HTMLImageElement { + new(): Image; +} \ No newline at end of file diff --git a/bridge/core/html/image.h b/bridge/core/html/image.h new file mode 100644 index 0000000000..cdc62d02fc --- /dev/null +++ b/bridge/core/html/image.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_HTML_IMAGE_H_ +#define WEBF_CORE_HTML_IMAGE_H_ + +#include "html_image_element.h" + +namespace webf { + +class Image : public HTMLImageElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + static Image* Create(ExecutingContext* context, ExceptionState& exception_state); + + explicit Image(ExecutingContext* context, ExceptionState& exception_state); + + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + + private: +}; + +} // namespace webf + +#endif // WEBF_CORE_HTML_IMAGE_H_ diff --git a/bridge/core/html/legacy/html_collection.cc b/bridge/core/html/legacy/html_collection.cc new file mode 100644 index 0000000000..ce10a39f75 --- /dev/null +++ b/bridge/core/html/legacy/html_collection.cc @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "html_collection.h" +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "core/dom/container_node.h" + +namespace webf { + +HTMLCollection::HTMLCollection(ContainerNode* base, CollectionType type) + : base_(base), type_(type), ScriptWrappable(base->ctx()) {} + +unsigned int HTMLCollection::length() const { + int32_t length = 0; + + if (type_ == CollectionType::kDocAll) { + auto* document = DynamicTo<Document>(*base_); + for (const Node& child : NodeTraversal::InclusiveDescendantsOf(*document->documentElement())) { + length++; + } + } + + return length; +} + +Element* HTMLCollection::item(unsigned int offset, ExceptionState& exception_state) const { + if (type_ == CollectionType::kDocAll) { + int32_t i = 0; + auto* document = DynamicTo<Document>(*base_); + for (Node& child : ElementTraversal ::InclusiveDescendantsOf(*document->documentElement())) { + if (i == offset) { + return DynamicTo<Element>(child); + } + i++; + } + } + + return nullptr; +} + +bool HTMLCollection::NamedPropertyQuery(const AtomicString& key, ExceptionState&) { + int32_t index = std::stoi(key.ToStdString()); + return index >= 0 && index < nodes_.size(); +} + +void HTMLCollection::NamedPropertyEnumerator(std::vector<AtomicString>& names, ExceptionState&) { + names.emplace_back(AtomicString(ctx(), "length")); + for (int i = 0; i < nodes_.size(); i++) { + names.emplace_back(AtomicString(ctx(), std::to_string(i))); + } +} + +void HTMLCollection::Trace(GCVisitor* visitor) const {} + +} // namespace webf diff --git a/bridge/core/html/legacy/html_collection.h b/bridge/core/html/legacy/html_collection.h new file mode 100644 index 0000000000..7f169cf590 --- /dev/null +++ b/bridge/core/html/legacy/html_collection.h @@ -0,0 +1,32 @@ +#ifndef BRIDGE_CORE_HTML_HTML_COLLECTION_H_ +#define BRIDGE_CORE_HTML_HTML_COLLECTION_H_ + +#include "bindings/qjs/heap_hashmap.h" +#include "bindings/qjs/heap_vector.h" +#include "bindings/qjs/script_wrappable.h" +#include "core/dom/collection_items_cache.h" +#include "core/dom/live_node_list_base.h" + +namespace webf { + +class HTMLCollection : public ScriptWrappable { + public: + HTMLCollection(ContainerNode* base, CollectionType); + + // DOM API + unsigned length() const; + Element* item(unsigned offset, ExceptionState& exception_state) const; + bool NamedPropertyQuery(const AtomicString&, ExceptionState&); + void NamedPropertyEnumerator(std::vector<AtomicString>& names, ExceptionState&); + + void Trace(GCVisitor*) const override; + + private: + CollectionType type_; + ContainerNode* base_; + std::vector<Element> nodes_; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_HTML_HTML_COLLECTION_H_ diff --git a/bridge/core/html/legacy/html_collection_test.cc b/bridge/core/html/legacy/html_collection_test.cc new file mode 100644 index 0000000000..5fec8e8131 --- /dev/null +++ b/bridge/core/html/legacy/html_collection_test.cc @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "gtest/gtest.h" +#include "webf_test_env.h" + +using namespace webf; + +TEST(HTMLCollection, children) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "2 <div/> <p/>"); + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let div = document.createElement('div');" + "let text = document.createTextNode('1234');" + "let div2 = document.createElement('p');" + "document.body.appendChild(div);" + "document.body.appendChild(text);" + "document.body.appendChild(div2);" + "console.log(document.body.children.length, document.body.children[0], document.body.children[1]);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} diff --git a/bridge/core/html/parser/html_parser.cc b/bridge/core/html/parser/html_parser.cc new file mode 100644 index 0000000000..17120b46d1 --- /dev/null +++ b/bridge/core/html/parser/html_parser.cc @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include <utility> + +#include "core/dom/document.h" +#include "core/dom/element.h" +#include "core/dom/text.h" +#include "foundation/logging.h" +#include "html_parser.h" + +namespace webf { + +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->FlushUICommand(); + 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 { + WEBF_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 webf diff --git a/bridge/core/html/parser/html_parser.h b/bridge/core/html/parser/html_parser.h new file mode 100644 index 0000000000..43fa5226fc --- /dev/null +++ b/bridge/core/html/parser/html_parser.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_HTML_PARSER_H +#define BRIDGE_HTML_PARSER_H + +#include <third_party/gumbo-parser/src/gumbo.h> +#include <string> +#include "foundation/native_string.h" + +namespace webf { + +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 webf + +#endif // BRIDGE_HTML_PARSER_H diff --git a/bridge/core/html/script_type_names.json5 b/bridge/core/html/script_type_names.json5 new file mode 100644 index 0000000000..6b4ac32d4b --- /dev/null +++ b/bridge/core/html/script_type_names.json5 @@ -0,0 +1,17 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "script_type_names" + } + ] + }, + "data": [ + "classic", + "module", + "importmap", + "speculationrules", + "webbundle" + ] +} diff --git a/bridge/core/input/touch.cc b/bridge/core/input/touch.cc new file mode 100644 index 0000000000..1ff461e68b --- /dev/null +++ b/bridge/core/input/touch.cc @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "touch.h" +#include "bindings/qjs/cppgc/gc_visitor.h" + +namespace webf { + +Touch* Touch::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected<Touch>(context, exception_state); +} + +Touch* Touch::Create(ExecutingContext* context, + const std::shared_ptr<TouchInit>& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected<Touch>(context, initializer, exception_state); +} + +Touch* Touch::Create(ExecutingContext* context, NativeTouch* native_touch) { + return MakeGarbageCollected<Touch>(context, native_touch); +} + +Touch::Touch(ExecutingContext* context, ExceptionState& exception_state) : ScriptWrappable(context->ctx()) {} + +Touch::Touch(ExecutingContext* context, const std::shared_ptr<TouchInit>& initializer, ExceptionState& exception_state) + : ScriptWrappable(context->ctx()), + identifier_(initializer->identifier()), + target_(initializer->target()), + clientX_(initializer->clientX()), + clientY_(initializer->clientY()), + screenX_(initializer->screenX()), + screenY_(initializer->screenY()), + pageX_(initializer->pageX()), + pageY_(initializer->pageY()), + radiusX_(initializer->radiusX()), + radiusY_(initializer->radiusY()), + rotationAngle_(initializer->rotationAngle()), + force_(initializer->force()) {} + +Touch::Touch(ExecutingContext* context, NativeTouch* native_touch) + : ScriptWrappable(context->ctx()), + identifier_(native_touch->identifier), + clientX_(native_touch->clientX), + clientY_(native_touch->clientY), + screenX_(native_touch->screenX), + screenY_(native_touch->screenY), + pageX_(native_touch->pageX), + pageY_(native_touch->pageY), + radiusX_(native_touch->radiusX), + radiusY_(native_touch->radiusY), + rotationAngle_(native_touch->rotationAngle), + force_(native_touch->force), + altitude_angle_(native_touch->altitudeAngle), + azimuth_angle_(native_touch->azimuthAngle) {} + +double Touch::altitudeAngle() const { + return altitude_angle_; +} + +double Touch::azimuthAngle() const { + return azimuth_angle_; +} + +double Touch::clientX() const { + return clientX_; +} + +double Touch::clientY() const { + return clientY_; +} + +double Touch::force() const { + return force_; +} + +double Touch::identifier() const { + return identifier_; +} + +double Touch::pageX() const { + return pageX_; +} + +double Touch::pageY() const { + return pageY_; +} + +double Touch::radiusX() const { + return radiusX_; +} + +double Touch::radiusY() const { + return radiusY_; +} + +double Touch::rotationAngle() const { + return rotationAngle_; +} + +double Touch::screenX() const { + return screenX_; +} + +double Touch::screenY() const { + return screenY_; +} + +EventTarget* Touch::target() const { + return target_; +} + +void Touch::Trace(GCVisitor* visitor) const { + visitor->Trace(target_); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/input/touch.d.ts b/bridge/core/input/touch.d.ts new file mode 100644 index 0000000000..80f1caa757 --- /dev/null +++ b/bridge/core/input/touch.d.ts @@ -0,0 +1,22 @@ +import {EventTarget} from "../dom/events/event_target"; +import {TouchInit} from "./touch_init"; + +/** A single contact point on a touch-sensitive device. The contact point is commonly a finger or stylus and the device may be a touchscreen or trackpad. */ +interface Touch { + readonly altitudeAngle: number; + readonly azimuthAngle: number; + readonly clientX: number; + readonly clientY: number; + readonly force: number; + readonly identifier: number; + readonly pageX: number; + readonly pageY: number; + readonly radiusX: number; + readonly radiusY: number; + readonly rotationAngle: number; + readonly screenX: number; + readonly screenY: number; + readonly target: EventTarget; + + new(init?: TouchInit): Touch; +} \ No newline at end of file diff --git a/bridge/core/input/touch.h b/bridge/core/input/touch.h new file mode 100644 index 0000000000..c3bd121575 --- /dev/null +++ b/bridge/core/input/touch.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_INPUT_TOUCH_H_ +#define BRIDGE_CORE_INPUT_TOUCH_H_ + +#include "bindings/qjs/cppgc/member.h" +#include "bindings/qjs/script_wrappable.h" +#include "core/dom/events/event_target.h" +#include "qjs_touch_init.h" + +namespace webf { + +struct NativeTouch { + int64_t identifier; + NativeBindingObject* target; + double clientX; + double clientY; + double screenX; + double screenY; + double pageX; + double pageY; + double radiusX; + double radiusY; + double rotationAngle; + double force; + double altitudeAngle; + double azimuthAngle; +}; + +class Touch : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = Touch*; + static Touch* Create(ExecutingContext* context, ExceptionState& exception_state); + static Touch* Create(ExecutingContext* context, + const std::shared_ptr<TouchInit>& initializer, + ExceptionState& exception_state); + static Touch* Create(ExecutingContext* context, NativeTouch* native_touch); + + explicit Touch(ExecutingContext* context, ExceptionState& exception_state); + explicit Touch(ExecutingContext* context, + const std::shared_ptr<TouchInit>& initializer, + ExceptionState& exception_state); + explicit Touch(ExecutingContext* context, NativeTouch* native_touch); + + double altitudeAngle() const; + double azimuthAngle() const; + double clientX() const; + double clientY() const; + double force() const; + double identifier() const; + double pageX() const; + double pageY() const; + double radiusX() const; + double radiusY() const; + double rotationAngle() const; + double screenX() const; + double screenY() const; + EventTarget* target() const; + + void Trace(GCVisitor* visitor) const override; + + private: + double altitude_angle_; + double azimuth_angle_; + double clientX_; + double clientY_; + double force_; + double identifier_; + double pageX_; + double pageY_; + double radiusX_; + double radiusY_; + double rotationAngle_; + double screenX_; + double screenY_; + Member<EventTarget> target_; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_INPUT_TOUCH_H_ diff --git a/bridge/core/input/touch_init.d.ts b/bridge/core/input/touch_init.d.ts new file mode 100644 index 0000000000..d509a5360a --- /dev/null +++ b/bridge/core/input/touch_init.d.ts @@ -0,0 +1,20 @@ + +// @ts-ignore +import {EventTarget} from "../dom/events/event_target"; + +// @ts-ignore +@Dictionary() +export interface TouchInit { + identifier: double; + target: EventTarget; + clientX?: double; + clientY?: double; + screenX?: double; + screenY?: double; + pageX?: double; + pageY?: double; + radiusX?: double; + radiusY?: double; + rotationAngle?: double; + force?: double; +} diff --git a/bridge/core/input/touch_list.cc b/bridge/core/input/touch_list.cc new file mode 100644 index 0000000000..f975e4233a --- /dev/null +++ b/bridge/core/input/touch_list.cc @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "touch_list.h" +#include "touch.h" + +namespace webf { + +void TouchList::FromNativeTouchList(ExecutingContext* context, + TouchList* touch_list, + NativeTouchList* native_touch_list) { + MemberMutationScope mutation_scope{context}; + for (size_t i = 0; i < native_touch_list->length; i++) { + auto* touch = Touch::Create(context, &native_touch_list->touches[i]); + touch_list->values_.emplace_back(touch); + } +} + +TouchList::TouchList(ExecutingContext* context, NativeTouchList* native_touch_list) : ScriptWrappable(context->ctx()) { + FromNativeTouchList(context, this, native_touch_list); +} + +uint32_t TouchList::length() const { + return values_.size(); +} + +Touch* TouchList::item(uint32_t index, ExceptionState& exception_state) const { + return values_[index]; +} + +bool TouchList::SetItem(uint32_t index, Touch* touch, ExceptionState& exception_state) { + if (index >= values_.size()) { + values_.emplace_back(touch); + } else { + values_[index] = touch; + } + return true; +} + +bool TouchList::NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state) { + uint32_t index = std::stoi(key.ToStdString()); + return index >= 0 && index < values_.size(); +} + +void TouchList::NamedPropertyEnumerator(std::vector<AtomicString>& props, ExceptionState& exception_state) { + for (int i = 0; i < values_.size(); i++) { + props.emplace_back(AtomicString(ctx(), std::to_string(i))); + } +} + +void TouchList::Trace(GCVisitor* visitor) const { + for (auto& item : values_) { + item->Trace(visitor); + } +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/input/touch_list.d.ts b/bridge/core/input/touch_list.d.ts new file mode 100644 index 0000000000..cfe87033be --- /dev/null +++ b/bridge/core/input/touch_list.d.ts @@ -0,0 +1,9 @@ +/** A list of contact points on a touch surface. For example, if the user has three fingers on the touch surface (such as a screen or trackpad), the corresponding TouchList object would have one Touch object for each finger, for a total of three entries. */ +import {Touch} from "./touch"; + +interface TouchList { + readonly length: number; + item(index: number): Touch | null; + [index: number]: Touch; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/input/touch_list.h b/bridge/core/input/touch_list.h new file mode 100644 index 0000000000..436c34e14e --- /dev/null +++ b/bridge/core/input/touch_list.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_CORE_INPUT_TOUCH_LIST_H_ +#define BRIDGE_CORE_INPUT_TOUCH_LIST_H_ + +#include "touch.h" + +namespace webf { + +struct NativeTouchList { + int64_t length; + NativeTouch* touches; +}; + +class TouchList : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = TouchList*; + + static void FromNativeTouchList(ExecutingContext* context, TouchList* touch_list, NativeTouchList* native_touch_list); + + TouchList() = delete; + explicit TouchList(ExecutingContext* context, NativeTouchList* native_touch_list); + + uint32_t length() const; + Touch* item(uint32_t index, ExceptionState& exception_state) const; + bool SetItem(uint32_t index, Touch* touch, ExceptionState& exception_state); + + bool NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state); + void NamedPropertyEnumerator(std::vector<AtomicString>& props, ExceptionState& exception_state); + + void Trace(GCVisitor* visitor) const override; + + private: + std::vector<Member<Touch>> values_; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_INPUT_TOUCH_LIST_H_ diff --git a/bridge/core/page.cc b/bridge/core/page.cc new file mode 100644 index 0000000000..48d234d0a0 --- /dev/null +++ b/bridge/core/page.cc @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include <atomic> +#include <unordered_map> + +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/binding_initializer.h" +#include "core/dart_methods.h" +#include "core/dom/document.h" +#include "core/frame/window.h" +#include "core/html/html_html_element.h" +#include "core/html/parser/html_parser.h" +#include "event_factory.h" +#include "foundation/logging.h" +#include "page.h" +#include "polyfill.h" + +namespace webf { + +ConsoleMessageHandler WebFPage::consoleMessageHandler{nullptr}; + +webf::WebFPage** WebFPage::pageContextPool{nullptr}; + +WebFPage::WebFPage(int32_t contextId, + const JSExceptionHandler& handler, + const uint64_t* dart_methods, + int32_t dart_methods_length) + : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { + context_ = new ExecutingContext( + contextId, + [](ExecutingContext* context, const char* message) { + if (context->dartMethodPtr()->onJsError != nullptr) { + context->dartMethodPtr()->onJsError(context->contextId(), message); + } + WEBF_LOG(ERROR) << message << std::endl; + }, + this, dart_methods, dart_methods_length); +} + +bool WebFPage::parseHTML(const char* code, size_t length) { + if (!context_->IsContextValid()) + return false; + + MemberMutationScope scope{context_}; + + auto document_element = context_->document()->documentElement(); + if (!document_element) { + return false; + } + + HTMLParser::parseHTML(code, length, context_->document()->documentElement()); + + return true; +} + +NativeValue* WebFPage::invokeModuleEvent(const NativeString* native_module_name, + const char* eventType, + void* ptr, + NativeValue* extra) { + if (!context_->IsContextValid()) + return nullptr; + + MemberMutationScope scope{context_}; + + JSContext* ctx = context_->ctx(); + Event* event = nullptr; + if (ptr != nullptr) { + std::string type = std::string(eventType); + auto* rawEvent = static_cast<RawEvent*>(ptr); + event = EventFactory::Create(context_, AtomicString(ctx, type), rawEvent); + } + + ScriptValue extraObject = ScriptValue(ctx, *extra); + AtomicString module_name = AtomicString(ctx, native_module_name); + auto listener = context_->ModuleListeners()->listener(module_name); + + if (listener == nullptr) { + return nullptr; + } + + ScriptValue arguments[] = {event != nullptr ? event->ToValue() : ScriptValue::Empty(ctx), extraObject}; + ScriptValue result = listener->value()->Invoke(ctx, ScriptValue::Empty(ctx), 2, arguments); + if (result.IsException()) { + context_->HandleException(&result); + return nullptr; + } + + ExceptionState exception_state; + auto* return_value = static_cast<NativeValue*>(malloc(sizeof(NativeValue))); + NativeValue tmp = result.ToNative(exception_state); + if (exception_state.HasException()) { + context_->HandleException(exception_state); + return nullptr; + } + + memcpy(return_value, &tmp, sizeof(NativeValue)); + return return_value; +} + +void WebFPage::evaluateScript(const NativeString* script, const char* url, int startLine) { + if (!context_->IsContextValid()) + 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 WebFPage::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { + if (!context_->IsContextValid()) + return; + context_->EvaluateJavaScript(script, length, url, startLine); +} + +void WebFPage::evaluateScript(const char* script, size_t length, const char* url, int startLine) { + if (!context_->IsContextValid()) + return; + context_->EvaluateJavaScript(script, length, url, startLine); +} + +uint8_t* WebFPage::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { + if (!context_->IsContextValid()) + return nullptr; + return context_->DumpByteCode(script, length, url, byteLength); +} + +void WebFPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { + if (!context_->IsContextValid()) + return; + context_->EvaluateByteCode(bytes, byteLength); +} + +std::thread::id WebFPage::currentThread() const { + return ownerThreadId; +} + +WebFPage::~WebFPage() { +#if IS_TEST + if (disposeCallback != nullptr) { + disposeCallback(this); + } +#endif + delete context_; + WebFPage::pageContextPool[contextId] = nullptr; +} + +void WebFPage::reportError(const char* errmsg) { + handler_(context_, errmsg); +} + +} // namespace webf diff --git a/bridge/page.h b/bridge/core/page.h similarity index 65% rename from bridge/page.h rename to bridge/core/page.h index aa64bb1b85..671cfd711a 100644 --- a/bridge/page.h +++ b/bridge/core/page.h @@ -7,29 +7,35 @@ #define WEBF_JS_QJS_BRIDGE_H_ #include <quickjs/quickjs.h> -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/html_parser.h" -#include "include/webf_bridge.h" - #include <atomic> #include <deque> +#include <thread> #include <vector> +#include "core/executing_context.h" +#include "foundation/native_string.h" + namespace webf { class WebFPage; + using JSBridgeDisposeCallback = void (*)(WebFPage* bridge); +using ConsoleMessageHandler = std::function<void(void* ctx, const std::string& message, int logLevel)>; /// WebFPage is class which manage all js objects create by <WebF> flutter widget. /// Every <WebF> flutter widgets have a corresponding WebFPage, and all objects created by JavaScript are stored here, /// and there is no data sharing between objects between different WebFPages. -/// It's safe to allocate many WebFPages at the same times on one thread, but not safe for multi-threads, only one thread can enter to WebFPage at the same time. +/// It's safe to allocate many WebFPages at the same times on one thread, but not safe for multi-threads, only one +/// thread can enter to WebFPage at the same time. class WebFPage final { public: static webf::WebFPage** pageContextPool; static ConsoleMessageHandler consoleMessageHandler; WebFPage() = delete; - WebFPage(int32_t jsContext, const JSExceptionHandler& handler); + WebFPage(int32_t jsContext, + const JSExceptionHandler& handler, + const uint64_t* dart_methods, + int32_t dart_methods_length); ~WebFPage(); // Bytecodes which registered by webf plugins. @@ -43,9 +49,14 @@ class WebFPage final { uint8_t* dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength); void evaluateByteCode(uint8_t* bytes, size_t byteLength); - [[nodiscard]] webf::binding::qjs::ExecutionContext* getContext() const { return m_context; } + std::thread::id currentThread() const; + + [[nodiscard]] ExecutingContext* GetExecutingContext() const { return context_; } - void invokeModuleEvent(NativeString* moduleName, const char* eventType, void* event, NativeString* extra); + NativeValue* invokeModuleEvent(const NativeString* moduleName, + const char* eventType, + void* event, + NativeValue* extra); void reportError(const char* errmsg); int32_t contextId; @@ -55,10 +66,12 @@ class WebFPage 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 webf diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc new file mode 100644 index 0000000000..6a1b9cb125 --- /dev/null +++ b/bridge/core/script_state.cc @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "script_state.h" +#include "defined_properties_initializer.h" +#include "event_factory.h" +#include "html_element_factory.h" +#include "names_installer.h" + +namespace webf { + +thread_local JSRuntime* runtime_ = nullptr; +thread_local 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) { + names_installer::Init(ctx_); + DefinedPropertiesInitializer::Init(); + // Bump up the built-in classId. To make sure the created classId are larger than JS_CLASS_CUSTOM_CLASS_INIT_COUNT. + for (int i = 0; i < JS_CLASS_CUSTOM_CLASS_INIT_COUNT - JS_CLASS_GC_TRACKER + 2; i++) { + JSClassID id{0}; + JS_NewClassID(&id); + } + } +} + +JSRuntime* ScriptState::runtime() { + return runtime_; +} + +ScriptState::~ScriptState() { + ctx_invalid_ = true; + JS_FreeContext(ctx_); + + // Run GC to clean up remaining objects about m_ctx; + JS_RunGC(runtime_); + + if (--runningContexts == 0) { + // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. + DefinedPropertiesInitializer::Dispose(); + names_installer::Dispose(); + HTMLElementFactory::Dispose(); + EventFactory::Dispose(); + JS_FreeRuntime(runtime_); + runtime_ = nullptr; + } + ctx_ = nullptr; +} +} // namespace webf diff --git a/bridge/core/script_state.h b/bridge/core/script_state.h new file mode 100644 index 0000000000..edcfbcb53a --- /dev/null +++ b/bridge/core/script_state.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_CORE_SCRIPT_STATE_H_ +#define BRIDGE_CORE_SCRIPT_STATE_H_ + +#include <quickjs/quickjs.h> +#include <cassert> + +namespace webf { + +// 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 bool Invalid() const { return !ctx_invalid_; } + inline JSContext* ctx() { + assert(!ctx_invalid_ && "GetExecutingContext has been released"); + return ctx_; + } + static JSRuntime* runtime(); + + private: + bool ctx_invalid_{false}; + JSContext* ctx_{nullptr}; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_SCRIPT_STATE_H_ diff --git a/bridge/core/timing/performance.cc b/bridge/core/timing/performance.cc new file mode 100644 index 0000000000..b657cc730b --- /dev/null +++ b/bridge/core/timing/performance.cc @@ -0,0 +1,841 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "performance.h" +#include <chrono> +#include "bindings/qjs/converter_impl.h" +#include "bindings/qjs/script_value.h" +#include "core/executing_context.h" +#include "performance_entry.h" +#include "performance_mark.h" +#include "performance_measure.h" +#include "qjs_performance_measure_options.h" + +namespace webf { + +using namespace std::chrono; + +Performance::Performance(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} + +int64_t Performance::now(ExceptionState& exception_state) const { + auto now = std::chrono::system_clock::now(); + auto duration = std::chrono::duration_cast<std::chrono::microseconds>(now - GetExecutingContext()->timeOrigin()); + auto reducedDuration = std::floor(duration / 1000us) * 1000us; + return std::chrono::duration_cast<std::chrono::milliseconds>(reducedDuration).count(); +} + +int64_t Performance::timeOrigin() const { + return std::chrono::duration_cast<std::chrono::milliseconds>(GetExecutingContext()->timeOrigin().time_since_epoch()) + .count(); +} + +ScriptValue Performance::toJSON(ExceptionState& exception_state) const { + int64_t now_value = now(exception_state); + int64_t time_origin_value = timeOrigin(); + + JSValue object = JS_NewObject(ctx()); + JS_SetPropertyStr(ctx(), object, "now", Converter<IDLInt64>::ToValue(ctx(), now_value)); + JS_SetPropertyStr(ctx(), object, "timeOrigin", Converter<IDLInt64>::ToValue(ctx(), time_origin_value)); + ScriptValue result = ScriptValue(ctx(), object); + JS_FreeValue(ctx(), object); + return result; +} + +AtomicString Performance::___webf_navigation_summary__(ExceptionState& exception_state) const { + return AtomicString::Empty(); +} + +std::vector<Member<PerformanceEntry>> Performance::getEntries(ExceptionState& exception_state) { + return entries_; +} + +std::vector<Member<PerformanceEntry>> Performance::getEntriesByType(const AtomicString& entry_type, + ExceptionState& exception_state) { + std::vector<Member<PerformanceEntry>> result; + for (auto& entry : entries_) { + if (entry->entryType() == entry_type) { + result.emplace_back(entry); + }; + } + + return result; +} + +std::vector<Member<PerformanceEntry>> Performance::getEntriesByName(const AtomicString& name, + ExceptionState& exception_state) { + std::vector<Member<PerformanceEntry>> result; + for (auto& entry : entries_) { + if (entry->name() == name) { + result.emplace_back(entry); + }; + } + + return result; +} + +std::vector<Member<PerformanceEntry>> Performance::getEntriesByName(const AtomicString& name, + const AtomicString& entry_type, + ExceptionState& exception_state) { + std::vector<Member<PerformanceEntry>> result; + for (auto& entry : entries_) { + if (entry->name() == name && entry->entryType() == entry_type) { + result.emplace_back(entry); + } + } + return result; +} + +void Performance::mark(const AtomicString& name, ExceptionState& exception_state) { + auto* mark = PerformanceMark::Create(GetExecutingContext(), name, nullptr, exception_state); + entries_.emplace_back(mark); +} + +void Performance::mark(const AtomicString& name, + const std::shared_ptr<PerformanceMarkOptions>& options, + ExceptionState& exception_state) { + auto* mark = PerformanceMark::Create(GetExecutingContext(), name, options, exception_state); + entries_.emplace_back(mark); +} + +void Performance::clearMarks(ExceptionState& exception_state) { + auto it = std::begin(entries_); + + while (it != entries_.end()) { + if ((*it)->entryType() == performance_entry_names::kmark) { + (*it).Clear(); + entries_.erase(it); + } else { + it++; + } + } +} + +void Performance::clearMarks(const AtomicString& name, ExceptionState& exception_state) { + auto it = std::begin(entries_); + + while (it != std::end(entries_)) { + if ((*it)->entryType() == performance_entry_names::kmark && (*it)->name() == name) { + (*it).Clear(); + entries_.erase(it); + } else { + it++; + } + } +} + +void Performance::clearMeasures(ExceptionState& exception_state) { + auto it = std::begin(entries_); + + while (it != std::end(entries_)) { + if ((*it)->entryType() == performance_entry_names::kmeasure) { + (*it).Clear(); + entries_.erase(it); + } else { + it++; + } + } +} + +void Performance::clearMeasures(const AtomicString& name, ExceptionState& exception_state) { + auto it = std::begin(entries_); + + while (it != std::end(entries_)) { + if ((*it)->entryType() == performance_entry_names::kmeasure && (*it)->name() == name) { + (*it).Clear(); + entries_.erase(it); + } else { + it++; + } + } +} + +void Performance::Trace(GCVisitor* visitor) const { + for (auto& entries : entries_) { + visitor->Trace(entries); + } +} + +void Performance::measure(const AtomicString& measure_name, ExceptionState& exception_state) { + measure(measure_name, AtomicString::Empty(), AtomicString::Empty(), exception_state); +} + +void Performance::measure(const AtomicString& measure_name, + const AtomicString& start_mark, + ExceptionState& exception_state) { + measure(measure_name, start_mark, AtomicString::Empty(), exception_state); +} + +void Performance::measure(const AtomicString& measure_name, + const ScriptValue& start_mark_or_options, + ExceptionState& exception_state) { + if (start_mark_or_options.IsString()) { + measure(measure_name, start_mark_or_options.ToString(), exception_state); + } else { + auto&& options = + Converter<PerformanceMeasureOptions>::FromValue(ctx(), start_mark_or_options.QJSValue(), exception_state); + measure(measure_name, options->hasStart() ? options->start() : AtomicString::Empty(), + options->hasEnd() ? options->end() : AtomicString::Empty(), exception_state); + } +} + +void Performance::measure(const AtomicString& measure_name, + const ScriptValue& start_mark_or_options, + const AtomicString& end_mark, + ExceptionState& exception_state) { + if (start_mark_or_options.IsString()) { + measure(measure_name, start_mark_or_options.ToString(), end_mark, exception_state); + } else { + auto&& options = + Converter<PerformanceMeasureOptions>::FromValue(ctx(), start_mark_or_options.QJSValue(), exception_state); + measure(measure_name, options->hasStart() ? options->start() : AtomicString::Empty(), + options->hasEnd() ? options->end() : end_mark, exception_state); + } +} + +void Performance::measure(const AtomicString& measure_name, + const AtomicString& start_mark, + const AtomicString& end_mark, + ExceptionState& exception_state) { + if (start_mark.IsEmpty()) { + auto* measure = PerformanceMeasure::Create(GetExecutingContext(), measure_name, timeOrigin(), now(exception_state), + ScriptValue::Empty(ctx()), exception_state); + entries_.emplace_back(measure); + return; + } + + auto start_it = std::begin(entries_); + auto end_it = std::begin(entries_); + + if (end_mark.IsEmpty()) { + auto start_entry = std::find_if(start_it, entries_.end(), + [&start_mark](auto&& entry) -> bool { return entry->name() == start_mark; }); + auto* measure = PerformanceMeasure::Create(GetExecutingContext(), measure_name, (*start_entry)->startTime(), + now(exception_state), ScriptValue::Empty(ctx()), exception_state); + entries_.emplace_back(measure); + return; + } + + size_t start_mark_count = std::count_if(entries_.begin(), entries_.end(), + [&start_mark](auto&& entry) -> bool { return entry->name() == start_mark; }); + + if (start_mark_count == 0) { + exception_state.ThrowException( + ctx(), ErrorType::TypeError, + "Failed to execute 'measure' on 'Performance': The mark " + start_mark.ToStdString() + " does not exist."); + return; + } + + size_t end_mark_count = std::count_if(entries_.begin(), entries_.end(), + [end_mark](auto&& entry) -> bool { return entry->name() == end_mark; }); + + if (end_mark_count == 0) { + exception_state.ThrowException( + ctx(), ErrorType::TypeError, + "Failed to execute 'measure' on 'Performance': The mark " + end_mark.ToStdString() + " does not exist."); + return; + } + + if (start_mark_count != end_mark_count) { + exception_state.ThrowException(ctx(), ErrorType::TypeError, + "Failed to execute 'measure' on 'Performance': The mark " + + start_mark.ToStdString() + " and " + end_mark.ToStdString() + + " does not appear the same number of times"); + return; + } + + for (size_t i = 0; i < start_mark_count; i++) { + auto start_entry = std::find_if(start_it, entries_.end(), + [&start_mark](auto&& entry) -> bool { return entry->name() == start_mark; }); + + bool is_start_entry_has_unique_id = (*start_entry)->uniqueId() != PERFORMANCE_ENTRY_NONE_UNIQUE_ID; + + auto end_entry_comparator = [&end_mark, &start_entry, is_start_entry_has_unique_id](auto&& entry) -> bool { + if (is_start_entry_has_unique_id) { + return entry->uniqueId() == (*start_entry)->uniqueId() && entry->name() == end_mark; + } + return entry->name() == end_mark; + }; + + auto end_entry = std::find_if(start_entry, entries_.end(), end_entry_comparator); + + if (end_entry == entries_.end()) { + size_t startIndex = start_entry - entries_.begin(); + assert_m(false, ("Can not get endEntry. startIndex: " + std::to_string(startIndex) + + " startMark: " + start_mark.ToStdString() + " endMark: " + end_mark.ToStdString())); + } + + int64_t duration = (*end_entry)->startTime() - (*start_entry)->startTime(); + int64_t start_time = std::chrono::duration_cast<microseconds>(system_clock::now().time_since_epoch()).count(); + auto* measure = PerformanceMeasure::Create(GetExecutingContext(), measure_name, start_time, start_time + duration, + ScriptValue::Empty(ctx()), exception_state); + entries_.emplace_back(measure); + start_it = ++start_entry; + end_it = ++end_entry; + } +} + +// JSValue Performance::clearMarks(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// JSValue targetMark = JS_NULL; +// if (argc == 1) { +// targetMark = argv[0]; +// } +// +// auto* entries = performance->m_nativePerformance.entries; +// auto it = std::begin(*entries); +// +// while (it != entries->end()) { +// char* entryType = (*it)->entryType; +// if (strcmp(entryType, "mark") == 0) { +// if (JS_IsNull(targetMark)) { +// entries->erase(it); +// } else { +// std::string entryName = (*it)->name; +// std::string targetName = jsValueToStdString(ctx, targetMark); +// if (entryName == targetName) { +// entries->erase(it); +// } else { +// it++; +// }; +// } +// } else { +// it++; +// } +// } +// +// return JS_NULL; +//} +// JSValue Performance::clearMeasures(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// JSValue targetMark = JS_NULL; +// if (argc == 1) { +// targetMark = argv[0]; +// } +// +// auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// auto entries = performance->m_nativePerformance.entries; +// auto it = std::begin(*entries); +// +// while (it != entries->end()) { +// char* entryType = (*it)->entryType; +// if (strcmp(entryType, "measure") == 0) { +// if (JS_IsNull(targetMark)) { +// entries->erase(it); +// } else { +// std::string entryName = (*it)->name; +// std::string targetName = jsValueToStdString(ctx, targetMark); +// if (entryName == targetName) { +// entries->erase(it); +// } else { +// it++; +// } +// } +// } else { +// it++; +// } +// } +// +// return JS_NULL; +//} +// JSValue Performance::getEntries(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// auto entries = performance->getFullEntries(); +// +// size_t entriesSize = entries.size(); +// JSValue returnArray = JS_NewArray(ctx); +// JSValue pushMethod = JS_GetPropertyStr(ctx, returnArray, "push"); +// +// for (size_t i = 0; i < entriesSize; i++) { +// auto& entry = entries[i]; +// auto entryType = std::string(entry->entryType); +// JSValue v = buildPerformanceEntry(entryType, performance->m_context, entry); +// JS_Call(ctx, pushMethod, returnArray, 1, &v); +// JS_FreeValue(ctx, v); +// } +// +// JS_FreeValue(ctx, pushMethod); +// return returnArray; +//} +// JSValue Performance::getEntriesByName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// if (argc == 0) { +// return JS_ThrowTypeError( +// ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); +// } +// +// std::string targetName = jsValueToStdString(ctx, argv[0]); +// auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// auto entries = performance->getFullEntries(); +// JSValue targetEntriesArray = JS_NewArray(ctx); +// JSValue pushMethod = JS_GetPropertyStr(ctx, targetEntriesArray, "push"); +// +// for (auto& m_entries : entries) { +// if (m_entries->name == targetName) { +// std::string entryType = std::string(m_entries->entryType); +// JSValue entry = buildPerformanceEntry(entryType, performance->m_context, m_entries); +// JS_Call(ctx, pushMethod, targetEntriesArray, 1, &entry); +// } +// } +// +// JS_FreeValue(ctx, pushMethod); +// return targetEntriesArray; +//} +// JSValue Performance::getEntriesByType(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// if (argc == 0) { +// return JS_ThrowTypeError( +// ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); +// } +// +// std::string entryType = jsValueToStdString(ctx, argv[0]); +// auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// auto entries = performance->getFullEntries(); +// JSValue targetEntriesArray = JS_NewArray(ctx); +// JSValue pushMethod = JS_GetPropertyStr(ctx, targetEntriesArray, "push"); +// +// for (auto& m_entries : entries) { +// if (m_entries->entryType == entryType) { +// JSValue entry = buildPerformanceEntry(entryType, performance->m_context, m_entries); +// JS_Call(ctx, pushMethod, targetEntriesArray, 1, &entry); +// } +// } +// +// JS_FreeValue(ctx, pushMethod); +// return targetEntriesArray; +//} +// JSValue Performance::mark(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// if (argc != 1) { +// return JS_ThrowTypeError(ctx, +// "Failed to execute 'mark' on 'Performance': 1 argument required, but only 0 present."); +// } +// +// auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// std::string markName = jsValueToStdString(ctx, argv[0]); +// performance->m_nativePerformance.mark(markName); +// +// return JS_NULL; +//} +// JSValue Performance::measure(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// if (argc == 0) { +// return JS_ThrowTypeError(ctx, +// "Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 +// present."); +// } +// +// std::string name = jsValueToStdString(ctx, argv[0]); +// std::string startMark; +// std::string endMark; +// +// if (argc > 1) { +// if (!JS_IsUndefined(argv[1])) { +// startMark = jsValueToStdString(ctx, argv[1]); +// } +// } +// +// if (argc > 2) { +// endMark = jsValueToStdString(ctx, argv[2]); +// } +// +// auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// JSValue exception = JS_NULL; +// performance->internalMeasure(name, startMark, endMark, &exception); +// +// if (!JS_IsNull(exception)) +// return exception; +// +// return JS_NULL; +//} +// +// PerformanceEntry::PerformanceEntry(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) +// : HostObject(context, "PerformanceEntry"), m_nativePerformanceEntry(nativePerformanceEntry) {} +// +// PerformanceMark::PerformanceMark(ExecutionContext* context, std::string& name, int64_t startTime) +// : PerformanceEntry(context, +// new NativePerformanceEntry(name, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} +// PerformanceMark::PerformanceMark(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) +// : PerformanceEntry(context, nativePerformanceEntry) {} +// PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, +// std::string& name, +// int64_t startTime, +// int64_t duration) +// : PerformanceEntry( +// context, +// new NativePerformanceEntry(name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} +// PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) +// : PerformanceEntry(context, nativePerformanceEntry) {} +// void NativePerformance::mark(const std::string& markName) { +// int64_t startTime = std::chrono::duration_cast<microseconds>(system_clock::now().time_since_epoch()).count(); +// auto* nativePerformanceEntry = +// new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; +// entries->emplace_back(nativePerformanceEntry); +//} +// void NativePerformance::mark(const std::string& markName, int64_t startTime) { +// auto* nativePerformanceEntry = +// new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; +// entries->emplace_back(nativePerformanceEntry); +//} +// +// Performance::Performance(ExecutionContext* context) : HostObject(context, "Performance") {} +// void Performance::internalMeasure(const std::string& name, +// const std::string& startMark, +// const std::string& endMark, +// JSValue* exception) { +// auto entries = getFullEntries(); +// +// if (!startMark.empty() && !endMark.empty()) { +// size_t startMarkCount = +// std::count_if(entries.begin(), entries.end(), +// [&startMark](NativePerformanceEntry* entry) -> bool { return entry->name == startMark; }); +// +// if (startMarkCount == 0) { +// *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not +// exist.", +// startMark.c_str()); +// return; +// } +// +// size_t endMarkCount = +// std::count_if(entries.begin(), entries.end(), +// [&endMark](NativePerformanceEntry* entry) -> bool { return entry->name == endMark; }); +// +// if (endMarkCount == 0) { +// *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not +// exist.", +// endMark.c_str()); +// return; +// } +// +// if (startMarkCount != endMarkCount) { +// *exception = JS_ThrowTypeError( +// m_ctx, +// "Failed to execute 'measure' on 'Performance': The mark %s and %s does not appear the same number of times", +// startMark.c_str(), endMark.c_str()); +// return; +// } +// +// auto startIt = std::begin(entries); +// auto endIt = std::begin(entries); +// +// for (size_t i = 0; i < startMarkCount; i++) { +// auto startEntry = std::find_if(startIt, entries.end(), [&startMark](NativePerformanceEntry* entry) -> bool { +// return entry->name == startMark; +// }); +// +// bool isStartEntryHasUniqueId = (*startEntry)->uniqueId != PERFORMANCE_ENTRY_NONE_UNIQUE_ID; +// +// auto endEntryComparator = [&endMark, &startEntry, +// isStartEntryHasUniqueId](NativePerformanceEntry* entry) -> bool { +// if (isStartEntryHasUniqueId) { +// return entry->uniqueId == (*startEntry)->uniqueId && entry->name == endMark; +// } +// return entry->name == endMark; +// }; +// +// auto endEntry = std::find_if(startEntry, entries.end(), endEntryComparator); +// +// if (endEntry == entries.end()) { +// size_t startIndex = startEntry - entries.begin(); +// assert_m(false, ("Can not get endEntry. startIndex: " + std::to_string(startIndex) + +// " startMark: " + startMark + " endMark: " + endMark)); +// } +// +// int64_t duration = (*endEntry)->startTime - (*startEntry)->startTime; +// int64_t startTime = std::chrono::duration_cast<microseconds>(system_clock::now().time_since_epoch()).count(); +// auto* nativePerformanceEntry = +// new NativePerformanceEntry{name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; +// m_nativePerformance.entries->emplace_back(nativePerformanceEntry); +// startIt = ++startEntry; +// endIt = ++endEntry; +// } +// } +//} +// double Performance::now() const { +// auto now = std::chrono::system_clock::now(); +// auto duration = std::chrono::duration_cast<std::chrono::microseconds>(now - GetExecutingContext()->timeOrigin); +// auto reducedDuration = std::floor(duration / 1000us) * 1000us; +// return std::chrono::duration_cast<std::chrono::milliseconds>(reducedDuration).count(); +//} +// std::vector<NativePerformanceEntry*> Performance::getFullEntries() { +// auto* bridgeEntries = m_nativePerformance.entries; +//#if ENABLE_PROFILE +// if (getDartMethod()->getPerformanceEntries == nullptr) { +// return std::vector<NativePerformanceEntry*>(); +// } +// auto dartEntryList = getDartMethod()->getPerformanceEntries(m_context->getContextId()); +// if (dartEntryList == nullptr) +// return std::vector<NativePerformanceEntry*>(); +// auto dartEntityBytes = dartEntryList->entries; +// std::vector<NativePerformanceEntry*> dartEntries; +// dartEntries.reserve(dartEntryList->length); +// +// for (size_t i = 0; i < dartEntryList->length * 3; i += 3) { +// const char* name = reinterpret_cast<const char*>(dartEntityBytes[i]); +// int64_t startTime = dartEntityBytes[i + 1]; +// int64_t uniqueId = dartEntityBytes[i + 2]; +// auto* nativePerformanceEntry = new NativePerformanceEntry(name, "mark", startTime, 0, uniqueId); +// dartEntries.emplace_back(nativePerformanceEntry); +// } +//#endif +// +// std::vector<NativePerformanceEntry*> mergedEntries; +// +// mergedEntries.insert(mergedEntries.end(), bridgeEntries->begin(), bridgeEntries->end()); +//#if ENABLE_PROFILE +// mergedEntries.insert(mergedEntries.end(), dartEntries.begin(), dartEntries.end()); +// delete[] dartEntryList->entries; +// delete dartEntryList; +//#endif +// +// return mergedEntries; +//} +// +//#if ENABLE_PROFILE +// +// void Performance::measureSummary(JSValue* exception) { +// internalMeasure(PERF_WIDGET_CREATION_COST, PERF_CONTROLLER_INIT_START, PERF_CONTROLLER_INIT_END, exception); +// internalMeasure(PERF_CONTROLLER_PROPERTIES_INIT_COST, PERF_CONTROLLER_INIT_START, PERF_CONTROLLER_PROPERTY_INIT, +// exception); +// internalMeasure(PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST, PERF_VIEW_CONTROLLER_INIT_START, +// PERF_VIEW_CONTROLLER_PROPERTY_INIT, exception); +// internalMeasure(PERF_BRIDGE_INIT_COST, PERF_BRIDGE_INIT_START, PERF_BRIDGE_INIT_END, exception); +// internalMeasure(PERF_BRIDGE_REGISTER_DART_METHOD_COST, PERF_BRIDGE_REGISTER_DART_METHOD_START, +// PERF_BRIDGE_REGISTER_DART_METHOD_END, exception); +// internalMeasure(PERF_CREATE_VIEWPORT_COST, PERF_CREATE_VIEWPORT_START, PERF_CREATE_VIEWPORT_END, exception); +// internalMeasure(PERF_ELEMENT_MANAGER_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, PERF_ELEMENT_MANAGER_INIT_END, +// exception); +// internalMeasure(PERF_ELEMENT_MANAGER_PROPERTIES_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, +// PERF_ELEMENT_MANAGER_PROPERTY_INIT, exception); +// internalMeasure(PERF_ROOT_ELEMENT_INIT_COST, PERF_ROOT_ELEMENT_INIT_START, PERF_ROOT_ELEMENT_INIT_END, exception); +// internalMeasure(PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST, PERF_ROOT_ELEMENT_INIT_START, +// PERF_ROOT_ELEMENT_PROPERTY_INIT, +// exception); +// internalMeasure(PERF_JS_CONTEXT_INIT_COST, PERF_JS_CONTEXT_INIT_START, PERF_JS_CONTEXT_INIT_END, exception); +// internalMeasure(PERF_JS_HOST_CLASS_GET_PROPERTY_COST, PERF_JS_HOST_CLASS_GET_PROPERTY_START, +// PERF_JS_HOST_CLASS_GET_PROPERTY_END, exception); +// internalMeasure(PERF_JS_HOST_CLASS_SET_PROPERTY_COST, PERF_JS_HOST_CLASS_SET_PROPERTY_START, +// PERF_JS_HOST_CLASS_SET_PROPERTY_END, exception); +// internalMeasure(PERF_JS_HOST_CLASS_INIT_COST, PERF_JS_HOST_CLASS_INIT_START, PERF_JS_HOST_CLASS_INIT_END, +// exception); internalMeasure(PERF_JS_NATIVE_FUNCTION_CALL_COST, PERF_JS_NATIVE_FUNCTION_CALL_START, +// PERF_JS_NATIVE_FUNCTION_CALL_END, exception); +// internalMeasure(PERF_JS_NATIVE_METHOD_INIT_COST, PERF_JS_NATIVE_METHOD_INIT_START, PERF_JS_NATIVE_METHOD_INIT_END, +// exception); +// internalMeasure(PERF_JS_POLYFILL_INIT_COST, PERF_JS_POLYFILL_INIT_START, PERF_JS_POLYFILL_INIT_END, exception); +// internalMeasure(PERF_JS_BUNDLE_LOAD_COST, PERF_JS_BUNDLE_LOAD_START, PERF_JS_BUNDLE_LOAD_END, exception); +// internalMeasure(PERF_JS_BUNDLE_EVAL_COST, PERF_JS_BUNDLE_EVAL_START, PERF_JS_BUNDLE_EVAL_END, exception); +// internalMeasure(PERF_FLUSH_UI_COMMAND_COST, PERF_FLUSH_UI_COMMAND_START, PERF_FLUSH_UI_COMMAND_END, exception); +// internalMeasure(PERF_CREATE_ELEMENT_COST, PERF_CREATE_ELEMENT_START, PERF_CREATE_ELEMENT_END, exception); +// internalMeasure(PERF_CREATE_TEXT_NODE_COST, PERF_CREATE_TEXT_NODE_START, PERF_CREATE_TEXT_NODE_END, exception); +// internalMeasure(PERF_CREATE_COMMENT_COST, PERF_CREATE_COMMENT_START, PERF_CREATE_COMMENT_END, exception); +// internalMeasure(PERF_DISPOSE_EVENT_TARGET_COST, PERF_DISPOSE_EVENT_TARGET_START, PERF_DISPOSE_EVENT_TARGET_END, +// exception); +// internalMeasure(PERF_ADD_EVENT_COST, PERF_ADD_EVENT_START, PERF_ADD_EVENT_END, exception); +// internalMeasure(PERF_INSERT_ADJACENT_NODE_COST, PERF_INSERT_ADJACENT_NODE_START, PERF_INSERT_ADJACENT_NODE_END, +// exception); +// internalMeasure(PERF_REMOVE_NODE_COST, PERF_REMOVE_NODE_START, PERF_REMOVE_NODE_END, exception); +// internalMeasure(PERF_SET_STYLE_COST, PERF_SET_STYLE_START, PERF_SET_STYLE_END, exception); +// internalMeasure(PERF_SET_PROPERTIES_COST, PERF_SET_PROPERTIES_START, PERF_SET_PROPERTIES_END, exception); +// internalMeasure(PERF_REMOVE_PROPERTIES_COST, PERF_REMOVE_PROPERTIES_START, PERF_REMOVE_PROPERTIES_END, exception); +// internalMeasure(PERF_FLEX_LAYOUT_COST, PERF_FLEX_LAYOUT_START, PERF_FLEX_LAYOUT_END, exception); +// internalMeasure(PERF_FLOW_LAYOUT_COST, PERF_FLOW_LAYOUT_START, PERF_FLOW_LAYOUT_END, exception); +// internalMeasure(PERF_INTRINSIC_LAYOUT_COST, PERF_INTRINSIC_LAYOUT_START, PERF_INTRINSIC_LAYOUT_END, exception); +// internalMeasure(PERF_SILVER_LAYOUT_COST, PERF_SILVER_LAYOUT_START, PERF_SILVER_LAYOUT_END, exception); +// internalMeasure(PERF_PAINT_COST, PERF_PAINT_START, PERF_PAINT_END, exception); +// internalMeasure(PERF_DOM_FORCE_LAYOUT_COST, PERF_DOM_FORCE_LAYOUT_START, PERF_DOM_FORCE_LAYOUT_END, exception); +// internalMeasure(PERF_DOM_FLUSH_UI_COMMAND_COST, PERF_DOM_FLUSH_UI_COMMAND_START, PERF_DOM_FLUSH_UI_COMMAND_END, +// exception); +// internalMeasure(PERF_JS_PARSE_TIME_COST, PERF_JS_PARSE_TIME_START, PERF_JS_PARSE_TIME_END, exception); +//} +// +// std::vector<NativePerformanceEntry*> findAllMeasures(const std::vector<NativePerformanceEntry*>& entries, +// const std::string& targetName) { +// std::vector<NativePerformanceEntry*> resultEntries; +// +// for (auto entry : entries) { +// if (entry->name == targetName) { +// resultEntries.emplace_back(entry); +// } +// } +// +// return resultEntries; +//}; +// +// double getMeasureTotalDuration(const std::vector<NativePerformanceEntry*>& measures) { +// double duration = 0.0; +// for (auto entry : measures) { +// duration += entry->duration; +// } +// return duration / 1000; +//} + +// JSValue Performance::__webf_navigation_summary__(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// JSValue exception = JS_NULL; +// performance->measureSummary(&exception); +// +// std::vector<NativePerformanceEntry*> entries = performance->getFullEntries(); +// +// if (entries.empty()) { +// return JS_ThrowTypeError(ctx, "Failed to get navigation summary: flutter is not running in profile mode."); +// } +// +// std::vector<NativePerformanceEntry*> measures; +// for (auto& m_entries : entries) { +// if (std::string(m_entries->entryType) == "measure") { +// measures.emplace_back(m_entries); +// } +// } +// +//#define GET_COST_WITH_DECREASE(NAME, MACRO, DECREASE) \ +// auto NAME##Measures = findAllMeasures(measures, MACRO); \ +// size_t NAME##Count = NAME##Measures.size(); \ +// double NAME##Cost = getMeasureTotalDuration(NAME##Measures) - (DECREASE); \ +// auto NAME##Avg = NAME##Measures.empty() ? 0 : (NAME##Cost) / NAME##Measures.size(); +// +//#define GET_COST(NAME, MACRO) \ +// auto NAME##Measures = findAllMeasures(measures, MACRO); \ +// size_t NAME##Count = NAME##Measures.size(); \ +// double NAME##Cost = getMeasureTotalDuration(NAME##Measures); \ +// auto NAME##Avg = NAME##Measures.empty() ? 0 : NAME##Cost / NAME##Measures.size(); +// +// GET_COST(widgetCreation, PERF_WIDGET_CREATION_COST); +// GET_COST(controllerPropertiesInit, PERF_CONTROLLER_PROPERTIES_INIT_COST); +// GET_COST(viewControllerPropertiesInit, PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST); +// GET_COST(bridgeInit, PERF_BRIDGE_INIT_COST); +// GET_COST(bridgeRegisterDartMethod, PERF_BRIDGE_REGISTER_DART_METHOD_COST); +// GET_COST(createViewport, PERF_CREATE_VIEWPORT_COST); +// GET_COST(elementManagerInit, PERF_ELEMENT_MANAGER_INIT_COST); +// GET_COST(elementManagerPropertiesInit, PERF_ELEMENT_MANAGER_PROPERTIES_INIT_COST); +// GET_COST(rootElementInit, PERF_ROOT_ELEMENT_INIT_COST); +// GET_COST(rootElementPropertiesInit, PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST); +// GET_COST(jsContextInit, PERF_JS_CONTEXT_INIT_COST); +// GET_COST(jsNativeMethodInit, PERF_JS_NATIVE_METHOD_INIT_COST); +// GET_COST(jsPolyfillInit, PERF_JS_POLYFILL_INIT_COST); +// GET_COST(jsBundleLoad, PERF_JS_BUNDLE_LOAD_COST); +// GET_COST(jsParseTime, PERF_JS_PARSE_TIME_COST); +// GET_COST(flushUiCommand, PERF_FLUSH_UI_COMMAND_COST); +// GET_COST(createElement, PERF_CREATE_ELEMENT_COST); +// GET_COST(createTextNode, PERF_CREATE_TEXT_NODE_COST); +// GET_COST(createComment, PERF_CREATE_COMMENT_COST); +// GET_COST(disposeEventTarget, PERF_DISPOSE_EVENT_TARGET_COST); +// GET_COST(addEvent, PERF_ADD_EVENT_COST); +// GET_COST(insertAdjacentNode, PERF_INSERT_ADJACENT_NODE_COST); +// GET_COST(removeNode, PERF_REMOVE_NODE_COST); +// GET_COST(setStyle, PERF_SET_STYLE_COST); +// GET_COST(setProperties, PERF_SET_PROPERTIES_COST); +// GET_COST(removeProperties, PERF_REMOVE_PROPERTIES_COST); +// GET_COST(flexLayout, PERF_FLEX_LAYOUT_COST); +// GET_COST(flowLayout, PERF_FLOW_LAYOUT_COST); +// GET_COST(intrinsicLayout, PERF_INTRINSIC_LAYOUT_COST); +// GET_COST(silverLayout, PERF_SILVER_LAYOUT_COST); +// GET_COST(paint, PERF_PAINT_COST); +// GET_COST(domForceLayout, PERF_DOM_FORCE_LAYOUT_COST); +// GET_COST(domFlushUICommand, PERF_DOM_FLUSH_UI_COMMAND_COST); +// GET_COST_WITH_DECREASE(jsHostClassGetProperty, PERF_JS_HOST_CLASS_GET_PROPERTY_COST, +// domForceLayoutCost + domFlushUICommandCost) +// GET_COST(jsHostClassSetProperty, PERF_JS_HOST_CLASS_SET_PROPERTY_COST); +// GET_COST(jsHostClassInit, PERF_JS_HOST_CLASS_INIT_COST); +// GET_COST(jsNativeFunction, PERF_JS_NATIVE_FUNCTION_CALL_COST); +// GET_COST_WITH_DECREASE(jsBundleEval, PERF_JS_BUNDLE_EVAL_COST, domForceLayoutCost + domFlushUICommandCost); +// +// double initBundleCost = jsBundleLoadCost + jsBundleEvalCost + flushUiCommandCost + createElementCost + +// createTextNodeCost + createCommentCost + disposeEventTargetCost + addEventCost + +// insertAdjacentNodeCost + removeNodeCost + setStyleCost + setPropertiesCost + +// removePropertiesCost; +// // layout and paint measure are not correct. +// double renderingCost = flexLayoutCost + flowLayoutCost + intrinsicLayoutCost + silverLayoutCost + paintCost; +// double totalCost = widgetCreationCost + initBundleCost; +// +// char buffer[5000]; +// // clang-format off +// sprintf(buffer, R"( +// Total time cost(without paint and layout): %.*fms +// +//%s: %.*fms +// + %s %.*fms +// + %s %.*fms +// + %s %.*fms +// + %s %.*fms +// + %s %.*fms +// + %s %.*fms +// + %s %.*fms +// + %s %.*fms +// + %s %.*fms +// + %s %.*fms +// + %s %.*fms +// + %s %.*fms +// First Bundle Load: %.*fms +// + %s %.*fms +// + %s %.*fms +// + %s %.*fms +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// Rendering: %.*fms +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +// + %s %.*fms avg: %.*fms count: %zu +//)", +// 2, totalCost, +// PERF_WIDGET_CREATION_COST, 2, widgetCreationCost, +// PERF_CONTROLLER_PROPERTIES_INIT_COST, 2, controllerPropertiesInitCost, +// PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST, 2, viewControllerPropertiesInitCost, +// PERF_ELEMENT_MANAGER_INIT_COST, 2, elementManagerInitCost, +// PERF_ELEMENT_MANAGER_PROPERTY_INIT, 2, elementManagerPropertiesInitCost, +// PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST, 2, rootElementPropertiesInitCost, +// PERF_ROOT_ELEMENT_INIT_COST, 2, rootElementInitCost, +// PERF_CREATE_VIEWPORT_COST, 2, createViewportCost, +// PERF_BRIDGE_INIT_COST, 2, bridgeInitCost, +// PERF_BRIDGE_REGISTER_DART_METHOD_COST, 2, bridgeRegisterDartMethodCost, +// PERF_JS_CONTEXT_INIT_COST, 2, jsContextInitCost, +// PERF_JS_NATIVE_METHOD_INIT_COST, 2, jsNativeMethodInitCost, +// PERF_JS_POLYFILL_INIT_COST, 2, jsPolyfillInitCost, +// 2, initBundleCost, +// PERF_JS_BUNDLE_LOAD_COST, 2, jsBundleLoadCost, +// PERF_JS_BUNDLE_EVAL_COST, 2, jsBundleEvalCost, +// PERF_JS_PARSE_TIME_COST, 2, jsParseTimeCost, +// PERF_FLUSH_UI_COMMAND_COST, 2, flushUiCommandCost, 2, flushUiCommandAvg, flushUiCommandCount, +// PERF_CREATE_ELEMENT_COST, 2, createElementCost, 2, createElementAvg, createElementCount, +// PERF_JS_HOST_CLASS_GET_PROPERTY_COST, 2, jsHostClassGetPropertyCost, 2, jsHostClassGetPropertyAvg, +// jsHostClassGetPropertyCount, PERF_JS_HOST_CLASS_SET_PROPERTY_COST, 2, jsHostClassSetPropertyCost, 2, +// jsHostClassSetPropertyAvg, jsHostClassSetPropertyCount, PERF_JS_HOST_CLASS_INIT_COST, 2, +// jsHostClassInitCost, 2, jsHostClassInitAvg, jsHostClassInitCount, PERF_JS_NATIVE_FUNCTION_CALL_COST, 2, +// jsNativeFunctionCost, 2, jsNativeFunctionAvg, jsNativeFunctionCount, PERF_CREATE_TEXT_NODE_COST, 2, +// createTextNodeCost, 2, createTextNodeAvg, createTextNodeCount, PERF_CREATE_COMMENT_COST, 2, +// createCommentCost, 2, createCommentAvg, createCommentCount, PERF_DISPOSE_EVENT_TARGET_COST, 2, +// disposeEventTargetCost, 2, disposeEventTargetAvg, disposeEventTargetCount, PERF_ADD_EVENT_COST, 2, +// addEventCost, 2, addEventAvg, addEventCount, PERF_INSERT_ADJACENT_NODE_COST, 2, insertAdjacentNodeCost, 2, +// insertAdjacentNodeAvg, insertAdjacentNodeCount, PERF_REMOVE_NODE_COST, 2, removeNodeCost, 2, removeNodeAvg, +// removeNodeCount, PERF_SET_STYLE_COST, 2, setStyleCost, 2, setStyleAvg, setStyleCount, +// PERF_DOM_FORCE_LAYOUT_COST, 2, domForceLayoutCost, 2, domForceLayoutAvg, domForceLayoutCount, +// PERF_DOM_FLUSH_UI_COMMAND_COST, 2, domFlushUICommandCost, 2, domFlushUICommandAvg, domFlushUICommandCount, +// PERF_SET_PROPERTIES_COST, 2, setPropertiesCost, 2, setPropertiesAvg, setPropertiesCount, +// PERF_REMOVE_PROPERTIES_COST, 2, removePropertiesCost, 2, removePropertiesAvg, removePropertiesCount, +// 2, renderingCost, +// PERF_FLEX_LAYOUT_COST, 2, flexLayoutCost, 2, flexLayoutAvg, flexLayoutCount, +// PERF_FLOW_LAYOUT_COST, 2, flowLayoutCost, 2, flowLayoutAvg, flowLayoutCount, +// PERF_INTRINSIC_LAYOUT_COST, 2, intrinsicLayoutCost, 2, intrinsicLayoutAvg, intrinsicLayoutCount, +// PERF_SILVER_LAYOUT_COST, 2, silverLayoutCost, 2, silverLayoutAvg, silverLayoutCount, +// PERF_PAINT_COST, 2, paintCost, 2, paintAvg, paintCount +// ); +// // clang-format on +// return JS_NewString(ctx, buffer); +//} + +//#endif + +} // namespace webf diff --git a/bridge/core/timing/performance.d.ts b/bridge/core/timing/performance.d.ts new file mode 100644 index 0000000000..9f7ccf0e76 --- /dev/null +++ b/bridge/core/timing/performance.d.ts @@ -0,0 +1,22 @@ +import {PerformanceMarkOptions} from "./performance_mark_options"; +import {PerformanceMeasureOptions} from "./performance_measure_options"; + +interface Performance { + now(): int64; + __webf_navigation_summary__(): string; + toJSON(): any; + + getEntries(): PerformanceEntry[]; + getEntriesByType(entryType: string): PerformanceEntry[]; + getEntriesByName(name: string, type?: string): PerformanceEntry[]; + + mark(name: string, options?: PerformanceMarkOptions): void; + measure(name: string): void; + measure(name: string, startMark?: any): void; + measure(name: string, startMark?: any, endMark?: string): void; + clearMarks(name?: string): void; + clearMeasures(name?: string): void; + + readonly timeOrigin: int64; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/timing/performance.h b/bridge/core/timing/performance.h new file mode 100644 index 0000000000..0f10cdd610 --- /dev/null +++ b/bridge/core/timing/performance.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_PERFORMANCE_H +#define BRIDGE_PERFORMANCE_H + +#include <vector> +#include "bindings/qjs/cppgc/member.h" +#include "bindings/qjs/script_wrappable.h" +#include "core/binding_object.h" +#include "performance_entry.h" +#include "qjs_performance_mark_options.h" + +namespace webf { + +class PerformanceEntry; + +struct NativePerformanceEntry { + int64_t name; + int64_t startTime; + int64_t uniqueId; +}; + +class Performance : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + Performance() = delete; + explicit Performance(ExecutingContext* context); + + int64_t now(ExceptionState& exception_state) const; + int64_t timeOrigin() const; + ScriptValue toJSON(ExceptionState& exception_state) const; + AtomicString ___webf_navigation_summary__(ExceptionState& exception_state) const; + std::vector<Member<PerformanceEntry>> getEntries(ExceptionState& exception_state); + std::vector<Member<PerformanceEntry>> getEntriesByType(const AtomicString& entry_type, + ExceptionState& exception_state); + std::vector<Member<PerformanceEntry>> getEntriesByName(const AtomicString& name, ExceptionState& exception_state); + std::vector<Member<PerformanceEntry>> getEntriesByName(const AtomicString& name, + const AtomicString& entry_type, + ExceptionState& exception_state); + + void mark(const AtomicString& name, ExceptionState& exception_state); + void mark(const AtomicString& name, + const std::shared_ptr<PerformanceMarkOptions>& options, + ExceptionState& exception_state); + void clearMarks(ExceptionState& exception_state); + void clearMarks(const AtomicString& name, ExceptionState& exception_state); + void clearMeasures(ExceptionState& exception_state); + void clearMeasures(const AtomicString& name, ExceptionState& exception_state); + + void measure(const AtomicString& measure_name, ExceptionState& exception_state); + void measure(const AtomicString& measure_name, const AtomicString& start_mark, ExceptionState& exception_state); + void measure(const AtomicString& measure_name, + const ScriptValue& start_mark_or_options, + ExceptionState& exception_state); + void measure(const AtomicString& measure_name, + const ScriptValue& start_mark_or_options, + const AtomicString& end_mark, + ExceptionState& exception_state); + + void Trace(GCVisitor* visitor) const override; + + private: + void measure(const AtomicString& measure_name, + const AtomicString& start_mark, + const AtomicString& end_mark, + ExceptionState& exception_state); + + std::vector<Member<PerformanceEntry>> entries_; +}; + +} // namespace webf + +#endif // BRIDGE_PERFORMANCE_H diff --git a/bridge/core/timing/performance_entry.cc b/bridge/core/timing/performance_entry.cc new file mode 100644 index 0000000000..3692a4cad6 --- /dev/null +++ b/bridge/core/timing/performance_entry.cc @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "performance_entry.h" +#include "bindings/qjs/converter_impl.h" +#include "core/executing_context.h" + +namespace webf { + +PerformanceEntry::PerformanceEntry(ExecutingContext* context, + const AtomicString& name, + int64_t start_time, + int64_t end_time) + : ScriptWrappable(context->ctx()), name_(name), start_time_(start_time), duration_(end_time - start_time) {} + +PerformanceEntry::PerformanceEntry(int64_t duration, + ExecutingContext* context, + const AtomicString& name, + int64_t start_time) + : ScriptWrappable(context->ctx()), name_(name), start_time_(start_time), duration_(duration) {} + +const AtomicString PerformanceEntry::name() const { + return name_; +} + +int64_t PerformanceEntry::startTime() const { + return start_time_; +} + +int64_t PerformanceEntry::duration() const { + return duration_; +} + +int64_t PerformanceEntry::uniqueId() const { + return unique_id_; +} + +ScriptValue PerformanceEntry::toJSON(ExceptionState& exception_state) { + JSValue object = JS_NewObject(ctx()); + JS_SetPropertyStr(ctx(), object, "name", Converter<IDLDOMString>::ToValue(ctx(), name_)); + JS_SetPropertyStr(ctx(), object, "entryType", Converter<IDLDOMString>::ToValue(ctx(), entryType())); + JS_SetPropertyStr(ctx(), object, "startTime", Converter<IDLInt64>::ToValue(ctx(), start_time_)); + JS_SetPropertyStr(ctx(), object, "duration", Converter<IDLInt64>::ToValue(ctx(), duration_)); + ScriptValue result = ScriptValue(ctx(), object); + JS_FreeValue(ctx(), object); + return result; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/timing/performance_entry.d.ts b/bridge/core/timing/performance_entry.d.ts new file mode 100644 index 0000000000..08b5b13eb3 --- /dev/null +++ b/bridge/core/timing/performance_entry.d.ts @@ -0,0 +1,8 @@ +interface PerformanceEntry { + readonly name: string; + readonly entryType: string; + readonly startTime: int64; + readonly duration: int64; + toJSON(): any; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/timing/performance_entry.h b/bridge/core/timing/performance_entry.h new file mode 100644 index 0000000000..1bf33a3630 --- /dev/null +++ b/bridge/core/timing/performance_entry.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_TIMING_PERFORMANCE_ENTRY_H_ +#define WEBF_CORE_TIMING_PERFORMANCE_ENTRY_H_ + +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/cppgc/member.h" +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/script_wrappable.h" + +namespace webf { + +#define PERFORMANCE_ENTRY_NONE_UNIQUE_ID -1024 + +class PerformanceEntry : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = Member<PerformanceEntry>; + explicit PerformanceEntry(ExecutingContext* context, + const AtomicString& name, + int64_t start_time, + int64_t finish_time); + explicit PerformanceEntry(int64_t duration, ExecutingContext* context, const AtomicString& name, int64_t start_time); + + const AtomicString name() const; + virtual AtomicString entryType() const = 0; + int64_t startTime() const; + int64_t duration() const; + int64_t uniqueId() const; + + ScriptValue toJSON(ExceptionState& exception_state); + + private: + AtomicString name_; + int64_t start_time_; + int64_t duration_; + int64_t unique_id_ = PERFORMANCE_ENTRY_NONE_UNIQUE_ID; +}; + +} // namespace webf + +#endif // WEBF_CORE_TIMING_PERFORMANCE_ENTRY_H_ diff --git a/bridge/core/timing/performance_entry_names.json5 b/bridge/core/timing/performance_entry_names.json5 new file mode 100644 index 0000000000..6f227ee012 --- /dev/null +++ b/bridge/core/timing/performance_entry_names.json5 @@ -0,0 +1,26 @@ +{ + metadata: { + "templates": [ + { + "template": "make_names", + "filename": "performance_entry_names" + } + ] + }, + data: [ +// "element", +// "event", +// "first-input", +// "largest-contentful-paint", +// "layout-shift", +// "longtask", + "mark", + "measure", +// "navigation", +// "paint", +// "resource", +// "taskattribution", +// "visibility-state", +// "back-forward-cache-restoration", + ], +} \ No newline at end of file diff --git a/bridge/core/timing/performance_mark.cc b/bridge/core/timing/performance_mark.cc new file mode 100644 index 0000000000..d7b4c4213f --- /dev/null +++ b/bridge/core/timing/performance_mark.cc @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "performance_mark.h" +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "bindings/qjs/script_value.h" +#include "performance.h" + +namespace webf { + +PerformanceMark* PerformanceMark::Create(ExecutingContext* context, + const AtomicString& name, + const std::shared_ptr<PerformanceMarkOptions>& mark_options, + ExceptionState& exception_state) { + auto* performance = context->performance(); + int64_t start = 0; + ScriptValue detail; + if (mark_options != nullptr) { + if (mark_options->hasStartTime()) { + start = mark_options->startTime(); + if (start < 0) { + exception_state.ThrowException(context->ctx(), ErrorType::TypeError, + "'" + name.ToStdString() + "' cannot have a negative start time."); + return nullptr; + } + } else { + start = performance->now(exception_state); + } + + if (mark_options->hasDetail()) { + detail = mark_options->detail(); + } + } else { + start = performance->now(exception_state); + } + + return MakeGarbageCollected<PerformanceMark>(context, name, start, detail); +} + +PerformanceMark::PerformanceMark(ExecutingContext* context, + const AtomicString& name, + int64_t start_time, + const ScriptValue& detail) + : PerformanceEntry(context, name, start_time, start_time), detail_(detail) {} + +AtomicString PerformanceMark::entryType() const { + return performance_entry_names::kmark; +} + +ScriptValue PerformanceMark::detail() const { + return detail_; +} + +void PerformanceMark::Trace(GCVisitor* visitor) const { + detail_.Trace(visitor); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/timing/performance_mark.d.ts b/bridge/core/timing/performance_mark.d.ts new file mode 100644 index 0000000000..1b2c7c24cd --- /dev/null +++ b/bridge/core/timing/performance_mark.d.ts @@ -0,0 +1,4 @@ +interface PerformanceMark extends PerformanceEntry { + readonly detail: any; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/timing/performance_mark.h b/bridge/core/timing/performance_mark.h new file mode 100644 index 0000000000..0c0e0ef17a --- /dev/null +++ b/bridge/core/timing/performance_mark.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_TIMING_PERFORMANCE_MARK_H_ +#define WEBF_CORE_TIMING_PERFORMANCE_MARK_H_ + +#include "bindings/qjs/script_value.h" +#include "performance_entry.h" +#include "performance_entry_names.h" +#include "qjs_performance_mark_options.h" + +namespace webf { + +class PerformanceMark : public PerformanceEntry { + DEFINE_WRAPPERTYPEINFO(); + + public: + static PerformanceMark* Create(ExecutingContext* context, + const AtomicString& name, + const std::shared_ptr<PerformanceMarkOptions>& mark_options, + ExceptionState& exception_state); + + explicit PerformanceMark(ExecutingContext* context, + const AtomicString& name, + int64_t start_time, + const ScriptValue& detail); + + AtomicString entryType() const override; + ScriptValue detail() const; + + void Trace(GCVisitor* visitor) const override; + + private: + ScriptValue detail_; +}; + +} // namespace webf + +#endif // WEBF_CORE_TIMING_PERFORMANCE_MARK_H_ diff --git a/bridge/core/timing/performance_mark_constants.json5 b/bridge/core/timing/performance_mark_constants.json5 new file mode 100644 index 0000000000..1d6c0fc083 --- /dev/null +++ b/bridge/core/timing/performance_mark_constants.json5 @@ -0,0 +1,135 @@ +{ + metadata: { + "templates": [ + { + "template": "make_names", + "filename": "performance_mark_constants" + } + ] + }, + data: [ + "widget_creation_cost", + "controller_properties_init_cost", + "view_controller_properties_init_cost", + "bridge_init_cost", + "bridge_register_dart_method_cost", + "create_viewport", + "element_manager_init_cost", + "element_manager_property_init_cost", + "root_element_init_cost", + "root_element_property_init_cost", + "js_context_init_cost", + "native_method_init_cost", + "polyfill_init_cost", + "js_bundle_load_cost", + "js_bundle_eval_cost", + "js_parse_time_cost", + "js_host_class_init_cost", + "js_native_function_call_cost", + "js_host_class_get_property_cost", + "js_host_class_set_property_cost", + "flush_ui_command_cost", + "create_element_cost", + "create_text_node_cost", + "create_comment_cost", + "dispose_event_target_cost", + "add_event_cost", + "insert_adjacent_node_cost", + "remove_node_cost", + "set_style_cost", + "parse_css_cost", + "parse_inline_css_cost", + "match_element_rule_cost", + "flush_style_cost", + "dom_force_layout_cost", + "dom_flush_ui_command_cost", + "set_properties_cost", + "remove_properties_cost", + "flex_layout_cost", + "flow_layout_cost", + "intrinsic_layout_cost", + "silver_layout_cost", + "paint_cost", + "controller_init_start", + "controller_init_end", + "controller_properties_init", + "view_controller_init_start", + "view_controller_property_init", + "bridge_init_start", + "bridge_init_end", + "bridge_register_dart_method_start", + "bridge_register_dart_method_end", + "create_viewport_start", + "create_viewport_end", + "element_manager_init_start", + "element_manager_init_end", + "element_manager_property_init", + "root_element_init_start", + "root_element_init_end", + "root_element_property_init", + "js_context_start", + "js_context_end", + "js_host_class_get_property_start", + "js_host_class_get_property_end", + "js_host_class_set_property_start", + "js_host_class_set_property_end", + "js_host_class_init_start", + "js_host_class_init_end", + "js_native_function_call_start", + "js_native_function_call_end", + "init_native_method_start", + "init_native_method_end", + "init_js_polyfill_start", + "init_js_polyfill_end", + "js_bundle_load_start", + "js_bundle_load_end", + "js_bundle_eval_start", + "js_bundle_eval_end", + "js_parse_time_start", + "js_parse_time_end", + "flush_ui_command_start", + "flush_ui_command_end", + "create_element_start", + "create_element_end", + "create_text_node_start", + "create_text_node_end", + "create_comment_start", + "create_comment_end", + "dispose_event_target_start", + "dispose_event_target_end", + "add_event_start", + "add_event_end", + "insert_adjacent_node_start", + "insert_adjacent_node_end", + "remove_node_start", + "remove_node_end", + "set_style_start", + "set_style_end", + "parse_css_start", + "parse_css_end", + "parse_inline_css_start", + "parse_inline_css_end", + "match_element_rule_start", + "match_element_rule_end", + "flush_style_start", + "flush_style_end", + "dom_force_layout_start", + "dom_force_layout_end", + "dom_flush_ui_command_start", + "dom_flush_ui_command_end", + "set_properties_start", + "set_properties_end", + "remove_properties_start", + "remove_properties_end", + "flex_layout_start", + "flex_layout_end", + "flow_layout_start", + "flow_layout_end", + "intrinsic_layout_start", + "intrinsic_layout_end", + "silver_layout_start", + "silver_layout_end", + "paint_start", + "paint_end" + ], +} \ No newline at end of file diff --git a/bridge/core/timing/performance_mark_options.d.ts b/bridge/core/timing/performance_mark_options.d.ts new file mode 100644 index 0000000000..ea029368e9 --- /dev/null +++ b/bridge/core/timing/performance_mark_options.d.ts @@ -0,0 +1,6 @@ +// @ts-ignore +@Dictionary() +export interface PerformanceMarkOptions { + detail?: any; + startTime?: double; +} diff --git a/bridge/core/timing/performance_measure.cc b/bridge/core/timing/performance_measure.cc new file mode 100644 index 0000000000..b5c64d6212 --- /dev/null +++ b/bridge/core/timing/performance_measure.cc @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "performance_measure.h" +#include "performance_entry_names.h" + +namespace webf { + +PerformanceMeasure* PerformanceMeasure::Create(ExecutingContext* context, + const AtomicString& name, + int64_t start_time, + int64_t end_time, + const ScriptValue& detail, + ExceptionState& exception_state) { + return MakeGarbageCollected<PerformanceMeasure>(context, name, start_time, end_time, detail, exception_state); +} + +PerformanceMeasure::PerformanceMeasure(ExecutingContext* context, + const AtomicString& name, + int64_t start_time, + int64_t end_time, + const ScriptValue& detail, + ExceptionState& exception_state) + : PerformanceEntry(end_time - start_time, context, name, start_time), detail_(detail) {} + +AtomicString PerformanceMeasure::entryType() const { + return performance_entry_names::kmeasure; +} + +ScriptValue PerformanceMeasure::detail() const { + return detail_; +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/timing/performance_measure.d.ts b/bridge/core/timing/performance_measure.d.ts new file mode 100644 index 0000000000..2d93e663ef --- /dev/null +++ b/bridge/core/timing/performance_measure.d.ts @@ -0,0 +1,4 @@ +interface PerformanceMeasure extends PerformanceEntry { + readonly detail: any; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/timing/performance_measure.h b/bridge/core/timing/performance_measure.h new file mode 100644 index 0000000000..8d009205db --- /dev/null +++ b/bridge/core/timing/performance_measure.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef WEBF_CORE_TIMING_PERFORMANCE_MEASURE_H_ +#define WEBF_CORE_TIMING_PERFORMANCE_MEASURE_H_ + +#include "bindings/qjs/script_value.h" +#include "core/executing_context.h" +#include "performance_entry.h" + +namespace webf { + +class PerformanceMeasure : public PerformanceEntry { + DEFINE_WRAPPERTYPEINFO(); + + public: + static PerformanceMeasure* Create(ExecutingContext* context, + const AtomicString& name, + int64_t start_time, + int64_t end_time, + const ScriptValue& detail, + ExceptionState& exception_state); + + explicit PerformanceMeasure(ExecutingContext* context, + const AtomicString& name, + int64_t start_time, + int64_t end_time, + const ScriptValue& detail, + ExceptionState& exception_state); + + ScriptValue detail() const; + + AtomicString entryType() const override; + + private: + ScriptValue detail_; +}; + +} // namespace webf + +#endif // WEBF_CORE_TIMING_PERFORMANCE_MEASURE_H_ diff --git a/bridge/core/timing/performance_measure_options.d.ts b/bridge/core/timing/performance_measure_options.d.ts new file mode 100644 index 0000000000..58e9910316 --- /dev/null +++ b/bridge/core/timing/performance_measure_options.d.ts @@ -0,0 +1,7 @@ +// @ts-ignore +@Dictionary() +export interface PerformanceMeasureOptions { + detail?: any; + start?: string; + end?: string; +} diff --git a/bridge/core/timing/performance_test.cc b/bridge/core/timing/performance_test.cc new file mode 100644 index 0000000000..67dafea283 --- /dev/null +++ b/bridge/core/timing/performance_test.cc @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "gtest/gtest.h" +#include "webf_test_env.h" + +using namespace webf; + +TEST(Performance, now) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::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) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = "console.log(performance.now() < 20);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Performance, timeOrigin) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "true 10"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "console.log(typeof performance.timeOrigin === 'number', performance.timeOrigin.toString().length);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Performance, toJSON) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::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) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let json = performance.toJSON();" + "console.log('now' in json, 'timeOrigin' in json);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Performance, mark) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "{detail: null}"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "performance.mark('abc');" + "let entries = performance.getEntries();" + "console.log(entries[0])"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Performance, markWithDetail) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "{detail: {...}}"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "performance.mark('abc', { detail: {value: 1}});" + "let entries = performance.getEntries();" + "console.log(entries[0])"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Performance, markWithName) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::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) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "performance.mark('abc', { detail: 1});" + "performance.mark('efg', { detail: 2});" + "performance.mark('abc', { detail: 3});" + "let entries = performance.getEntriesByName('abc');" + "console.log(entries)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Performance, clearMarks) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::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) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "performance.mark('abc', { detail: 1});" + "performance.mark('efg', { detail: 2});" + "performance.mark('abc', { detail: 3});" + "performance.clearMarks();" + "let entries = performance.getEntries();" + "console.log(entries);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Performance, clearMarksByName) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::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) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "performance.mark('abc', { detail: 1});" + "performance.mark('efg', { detail: 2});" + "performance.mark('abc', { detail: 3});" + "performance.clearMarks('abc');" + "let entries = performance.getEntries();" + "console.log(entries);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Performance, measure) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::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) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = R"( +performance.mark('A'); + +setTimeout(() => { +performance.mark('B'); +setTimeout(() => { + performance.measure("measure a to b", 'A', 'B'); + let duration = performance.getEntriesByType("measure")[0].duration; + console.log(duration > 90 && duration < 101); +}, 100); +}, 100); + +)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + TEST_runLoop(context); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} diff --git a/bridge/dart_methods.cc b/bridge/dart_methods.cc deleted file mode 100644 index 918dc78a19..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 "webf_bridge.h" - -namespace webf { - -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 webf diff --git a/bridge/foundation/ascii_types.h b/bridge/foundation/ascii_types.h new file mode 100644 index 0000000000..a3a7bc918f --- /dev/null +++ b/bridge/foundation/ascii_types.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2013, Opera Software ASA. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Opera Software ASA nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_FOUNDATION_ASCII_TYPES_H_ +#define BRIDGE_FOUNDATION_ASCII_TYPES_H_ + +namespace webf { + +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 webf + +#endif // BRIDGE_FOUNDATION_ASCII_TYPES_H_ diff --git a/bridge/foundation/casting.h b/bridge/foundation/casting.h new file mode 100644 index 0000000000..6c36a52f70 --- /dev/null +++ b/bridge/foundation/casting.h @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2013, Opera Software ASA. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Opera Software ASA nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_FOUNDATION_CASTING_H_ +#define BRIDGE_FOUNDATION_CASTING_H_ + +#include <cassert> +#include <type_traits> + +namespace webf { + +// 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 webf + +#endif // BRIDGE_FOUNDATION_CASTING_H_ diff --git a/bridge/foundation/closure.h b/bridge/foundation/closure.h deleted file mode 100644 index eb52a86c5d..0000000000 --- a/bridge/foundation/closure.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (C) 2020-present The Kraken authors. All rights reserved. - */ - -#ifndef BRIDGE_CLOSURE_H -#define BRIDGE_CLOSURE_H - -#include <functional> - -namespace fml { -using closure = std::function<void()>; -} - -#endif // BRIDGE_CLOSURE_H diff --git a/bridge/foundation/colors.h b/bridge/foundation/colors.h index 373a6f3b86..5da319bf3a 100644 --- a/bridge/foundation/colors.h +++ b/bridge/foundation/colors.h @@ -1,5 +1,6 @@ /* - * Copyright (C) 2019-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #ifndef BRIDGE_COLORS_H #define BRIDGE_COLORS_H diff --git a/bridge/foundation/inspector_task_queue.cc b/bridge/foundation/inspector_task_queue.cc index 6fa4d5e5eb..96a7fae989 100644 --- a/bridge/foundation/inspector_task_queue.cc +++ b/bridge/foundation/inspector_task_queue.cc @@ -5,9 +5,9 @@ #include "inspector_task_queue.h" -namespace foundation { +namespace webf { std::mutex InspectorTaskQueue::inspector_task_creation_mutex_{}; fml::RefPtr<InspectorTaskQueue> InspectorTaskQueue::instance_{}; -} // namespace foundation +} // namespace webf diff --git a/bridge/foundation/inspector_task_queue.h b/bridge/foundation/inspector_task_queue.h index c899ddffd7..e9a98e0770 100644 --- a/bridge/foundation/inspector_task_queue.h +++ b/bridge/foundation/inspector_task_queue.h @@ -7,9 +7,8 @@ #define BRIDGE_INSPECTOR_TASK_QUEUE_H #include "task_queue.h" -#include "webf_foundation.h" -namespace foundation { +namespace webf { class InspectorTaskQueue; using Task = void (*)(void*); @@ -26,7 +25,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; } @@ -36,6 +34,6 @@ class InspectorTaskQueue : public TaskQueue { static fml::RefPtr<InspectorTaskQueue> instance_; }; -} // namespace foundation +} // namespace webf #endif // BRIDGE_INSPECTOR_TASK_QUEUE_H diff --git a/bridge/foundation/logging.cc b/bridge/foundation/logging.cc index 65a0bac8bf..0d7a8ac4d1 100644 --- a/bridge/foundation/logging.cc +++ b/bridge/foundation/logging.cc @@ -1,12 +1,13 @@ /* - * Copyright (C) 2019-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "logging.h" #include <algorithm> #include "colors.h" -#include "page.h" +#include "core/page.h" #if defined(IS_ANDROID) #include <android/log.h> @@ -23,7 +24,7 @@ #include "inspector/impl/jsc_console_client_impl.h" #endif -namespace foundation { +namespace webf { namespace { const char* StripDots(const char* path) { @@ -42,7 +43,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 +95,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 +126,9 @@ void printLog(int32_t contextId, std::stringstream& stream, std::string level, v webf::WebFPage::consoleMessageHandler(ctx, stream.str(), static_cast<int>(_log_level)); } - if (webf::getDartMethod()->onJsLog != nullptr) { - webf::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 webf diff --git a/bridge/foundation/logging.h b/bridge/foundation/logging.h index f2905644b5..ac0760a75e 100644 --- a/bridge/foundation/logging.h +++ b/bridge/foundation/logging.h @@ -1,16 +1,29 @@ /* - * Copyright (C) 2022-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #ifndef FOUNDATION_LOGGING_H_ #define FOUNDATION_LOGGING_H_ #include <sstream> - #include <string> -#include "include/webf_bridge.h" -namespace foundation { +#define WEBF_LOG_STREAM(severity) ::webf::LogMessage(::webf::severity, __FILE__, __LINE__, nullptr).stream() + +#define WEBF_LAZY_STREAM(stream, condition) !(condition) ? (void)0 : ::webf::LogMessageVoidify() & (stream) + +#define WEBF_EAT_STREAM_PARAMETERS(ignored) \ + true || (ignored) ? (void)0 : ::LogMessageVoidify() & ::LogMessage(::LOG_FATAL, 0, 0, nullptr).stream() + +#define WEBF_LOG(severity) WEBF_LAZY_STREAM(WEBF_LOG_STREAM(severity), true) + +#define WEBF_CHECK(condition) \ + WEBF_LAZY_STREAM(::webf::LogMessage(::webf::FATAL, __FILE__, __LINE__, #condition).stream(), !(condition)) + +namespace webf { + +class ExecutingContext; typedef int LogSeverity; @@ -20,7 +33,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 +61,10 @@ class LogMessage { const LogSeverity severity_; const char* file_; const int line_; - - DISALLOW_COPY_AND_ASSIGN(LogMessage); }; -void printLog(int32_t contextId, std::stringstream& stream, std::string level, void* ctx); - -#define WEBF_LOG_STREAM(severity) ::foundation::LogMessage(::foundation::severity, __FILE__, __LINE__, nullptr).stream() - -#define WEBF_LAZY_STREAM(stream, condition) !(condition) ? (void)0 : foundation::LogMessageVoidify() & (stream) - -#define WEBF_LOG(severity) WEBF_LAZY_STREAM(WEBF_LOG_STREAM(severity), true) - -#define WEBF_CHECK(condition) WEBF_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 webf #endif // FOUNDATION_LOGGING_H_ diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h new file mode 100644 index 0000000000..df10440442 --- /dev/null +++ b/bridge/foundation/macros.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_MACROS_H +#define BRIDGE_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 WEBF_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete + +#define WEBF_DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete + +#define WEBF_DISALLOW_MOVE(TypeName) \ + TypeName(TypeName&&) = delete; \ + TypeName& operator=(TypeName&&) = delete + +#define WEBF_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 WEBF_STACK_ALLOCATED() \ + private: \ + void* operator new(size_t) = delete; \ + void* operator new(size_t, void*) = delete + +// WEBF_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 WEBF_DISALLOW_NEW() \ + public: \ + using IsDisallowNewMarker = int; \ + void* operator new(size_t, void* location) { return location; } \ + \ + private: \ + void* operator new(size_t) = delete; + +#define WEBF_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName& operator=(const TypeName&) = delete + +#define WEBF_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName(TypeName&&) = delete; \ + TypeName& operator=(const TypeName&) = delete; \ + TypeName& operator=(TypeName&&) = delete + +#define WEBF_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + WEBF_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) + +#endif // BRIDGE_MACROS_H diff --git a/bridge/foundation/native_string.cc b/bridge/foundation/native_string.cc new file mode 100644 index 0000000000..8408b73404 --- /dev/null +++ b/bridge/foundation/native_string.cc @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "native_string.h" +#include <string> + +namespace webf { + +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 webf diff --git a/bridge/foundation/native_string.h b/bridge/foundation/native_string.h new file mode 100644 index 0000000000..b909337832 --- /dev/null +++ b/bridge/foundation/native_string.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_NATIVE_STRING_H +#define BRIDGE_NATIVE_STRING_H + +#include <cinttypes> +#include <cstdlib> +#include <cstring> + +#include "foundation/macros.h" + +namespace webf { + +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 webf + +#endif // BRIDGE_NATIVE_STRING_H diff --git a/bridge/foundation/native_type.h b/bridge/foundation/native_type.h new file mode 100644 index 0000000000..a8094ce042 --- /dev/null +++ b/bridge/foundation/native_type.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_FOUNDATION_NATIVE_TYPE_H_ +#define BRIDGE_FOUNDATION_NATIVE_TYPE_H_ + +#include <type_traits> +#include <vector> +#include "bindings/qjs/qjs_function.h" +#include "bindings/qjs/script_value.h" +#include "foundation/native_string.h" + +namespace webf { + +// Shared C struct which can be read by dart through Dart FFI. +struct DartReadable {}; + +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<AtomicString> {}; + +// Int64 +struct NativeTypeInt64 final : public NativeTypeBaseHelper<int64_t> {}; + +// Double +struct NativeTypeDouble final : public NativeTypeBaseHelper<double> {}; + +// JSON +struct NativeTypeJSON final : public NativeTypeBaseHelper<ScriptValue> {}; + +// Array +template <typename T> +struct NativeTypeArray final : public NativeTypeBase { + using ImplType = typename std::vector<T>; +}; + +// 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 webf + +#endif // BRIDGE_FOUNDATION_NATIVE_TYPE_H_ diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc new file mode 100644 index 0000000000..51a707b219 --- /dev/null +++ b/bridge/foundation/native_value.cc @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "native_value.h" +#include "bindings/qjs/qjs_engine_patch.h" +#include "bindings/qjs/script_value.h" +#include "core/executing_context.h" + +namespace webf { + +NativeValue Native_NewNull() { + return (NativeValue){.u = {.int64 = 0}, .uint32 = 0, .tag = NativeTag::TAG_NULL}; +} + +NativeValue Native_NewString(NativeString* string) { + return (NativeValue){ + .u = {.ptr = static_cast<void*>(string)}, + .uint32 = 0, + .tag = NativeTag::TAG_STRING, + }; +} + +NativeValue Native_NewCString(const 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}, + .uint32 = 0, + .tag = NativeTag::TAG_FLOAT64, + }; +} + +NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr) { + return (NativeValue){.u = {.ptr = ptr}, .uint32 = static_cast<uint32_t>(pointerType), .tag = NativeTag::TAG_POINTER}; +} + +NativeValue Native_NewBool(bool value) { + return (NativeValue){ + .u = {.int64 = value ? 1 : 0}, + .uint32 = 0, + .tag = NativeTag::TAG_BOOL, + }; +} + +NativeValue Native_NewInt64(int64_t value) { + return (NativeValue){ + .u = {.int64 = value}, + .uint32 = 0, + .tag = NativeTag::TAG_INT, + }; +} + +NativeValue Native_NewList(uint32_t argc, NativeValue* argv) { + return (NativeValue){.u = {.ptr = reinterpret_cast<void*>(argv)}, .uint32 = argc, .tag = NativeTag::TAG_LIST}; +} + +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())}, + .uint32 = 0, + .tag = NativeTag::TAG_JSON, + }; + return result; +} + +} // namespace webf diff --git a/bridge/bindings/qjs/native_value.h b/bridge/foundation/native_value.h similarity index 53% rename from bridge/bindings/qjs/native_value.h rename to bridge/foundation/native_value.h index e3c3d46e6e..1c98c9385c 100644 --- a/bridge/bindings/qjs/native_value.h +++ b/bridge/foundation/native_value.h @@ -6,7 +6,13 @@ #ifndef BRIDGE_NATIVE_VALUE_H #define BRIDGE_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 webf { enum NativeTag { TAG_STRING = 0, @@ -21,49 +27,55 @@ enum NativeTag { TAG_ASYNC_FUNCTION = 9, }; -enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, NativeBoundingClientRect = 2, NativeCanvasRenderingContext2D = 3, NativeBindingObject = 4 }; +enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, Others = 2 }; -namespace webf::binding::qjs { +class ExecutingContext; +class ExceptionState; +class ScriptValue; // Exchange data struct between dart and C++ struct NativeValue { union { - uint64_t uint64; + int64_t int64; double float64; + void* ptr; } u; - int64_t int64; - int64_t tag; + uint32_t uint32; + int32_t tag; }; 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; }; NativeValue Native_NewNull(); NativeValue Native_NewString(NativeString* string); -NativeValue Native_NewCString(std::string string); +NativeValue Native_NewCString(const 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_NewList(uint32_t argc, NativeValue* argv); 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, ExceptionState& exception_state); -} // namespace webf::binding::qjs +} // namespace webf #endif // BRIDGE_NATIVE_VALUE_H diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h new file mode 100644 index 0000000000..0f853e57cd --- /dev/null +++ b/bridge/foundation/native_value_converter.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ +#define BRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ + +#include "bindings/qjs/script_wrappable.h" +#include "core/binding_object.h" +#include "native_type.h" +#include "native_value.h" + +namespace webf { + +// 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(const ImplType& value) { return Native_NewString(value.ToNativeString().release()); } + + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { + if (value.tag == NativeTag::TAG_NULL) { + return AtomicString::Empty(); + } + assert(value.tag == NativeTag::TAG_STRING); + return AtomicString(ctx, 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) { + assert(value.tag == NativeTag::TAG_BOOL); + 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) { + assert(value.tag == NativeTag::TAG_INT); + 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) { + assert(value.tag == NativeTag::TAG_FLOAT64); + 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, ExceptionState& exception_state) { + return Native_NewJSON(value, exception_state); + } + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { + assert(value.tag == NativeTag::TAG_JSON); + auto* str = static_cast<const char*>(value.u.ptr); + return ScriptValue::CreateJsonObject(ctx, str, strlen(str)); + } +}; + +class BindingObject; +struct DartReadable; + +template <typename T> +struct NativeValueConverter<NativeTypePointer<T>, std::enable_if_t<std::is_void_v<T>>> + : public NativeValueConverterBase<NativeTypePointer<T>> { + static NativeValue ToNativeValue(T* value) { return Native_NewPtr(JSPointerType::Others, value); } + static T* FromNativeValue(NativeValue value) { + assert(value.tag == NativeTag::TAG_POINTER); + return static_cast<T*>(value.u.ptr); + } + static T* FromNativeValue(JSContext* ctx, NativeValue value) { + assert(value.tag == NativeTag::TAG_POINTER); + return static_cast<T*>(value.u.ptr); + } +}; + +template <typename T> +struct NativeValueConverter<NativeTypePointer<T>, std::enable_if_t<std::is_base_of_v<DartReadable, T>>> + : public NativeValueConverterBase<NativeTypePointer<T>> { + static NativeValue ToNativeValue(T* value) { return Native_NewPtr(JSPointerType::Others, value); } + static T* FromNativeValue(NativeValue value) { + assert(value.tag == NativeTag::TAG_POINTER); + return static_cast<T*>(value.u.ptr); + } + static T* FromNativeValue(JSContext* ctx, NativeValue value) { + assert(value.tag == NativeTag::TAG_POINTER); + return static_cast<T*>(value.u.ptr); + } +}; + +template <typename T> +struct NativeValueConverter<NativeTypePointer<T>, std::enable_if_t<std::is_base_of_v<ScriptWrappable, T>>> + : public NativeValueConverterBase<T> { + static NativeValue ToNativeValue(T* value) { return Native_NewPtr(JSPointerType::Others, value->bindingObject()); } + static T* FromNativeValue(JSContext* ctx, NativeValue value) { + if (value.tag == NativeTag::TAG_NULL) { + return nullptr; + } + assert(value.tag == NativeTag::TAG_POINTER); + return DynamicTo<T>(BindingObject::From(static_cast<NativeBindingObject*>(value.u.ptr))); + } +}; + +template <> +struct NativeValueConverter<NativeTypeFunction> : public NativeValueConverterBase<NativeTypeFunction> { + static NativeValue ToNativeValue(ImplType value) { + // Not supported. + assert(false); + return Native_NewNull(); + } + + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { + assert(value.tag == NativeTag::TAG_FUNCTION); + return QJSFunction::Create(ctx, BindingObject::AnonymousFunctionCallback, 4, value.u.ptr); + }; +}; + +template <> +struct NativeValueConverter<NativeTypeAsyncFunction> : public NativeValueConverterBase<NativeTypeAsyncFunction> { + static NativeValue ToNativeValue(ImplType value) { + // Not supported. + assert(false); + return Native_NewNull(); + } + + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { + assert(value.tag == NativeTag::TAG_ASYNC_FUNCTION); + return QJSFunction::Create(ctx, BindingObject::AnonymousAsyncFunctionCallback, 4, value.u.ptr); + } +}; + +template <typename T> +struct NativeValueConverter<NativeTypeArray<T>> : public NativeValueConverterBase<NativeTypeArray<T>> { + using ImplType = typename NativeTypeArray<typename NativeValueConverter<T>::ImplType>::ImplType; + static NativeValue ToNativeValue(ImplType value) { + auto* ptr = new NativeValue[value.size()]; + for (int i = 0; i < value.size(); i++) { + ptr[i] = NativeValueConverter<T>::ToNativeValue(value[i]); + } + return Native_NewList(value.size(), ptr); + } + + static ImplType FromNativeValue(JSContext* ctx, NativeValue native_value) { + assert(native_value.tag == NativeTag::TAG_LIST); + size_t length = native_value.uint32; + auto* arr = static_cast<NativeValue*>(native_value.u.ptr); + std::vector<typename T::ImplType> vec; + vec.reserve(length); + for (int i = 0; i < length; i++) { + vec.emplace_back(NativeValueConverter<T>::FromNativeValue(ctx, arr[i])); + } + return vec; + } +}; + +} // namespace webf + +#endif // BRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ diff --git a/bridge/foundation/ref_counted_internal.h b/bridge/foundation/ref_counted_internal.h index d3e98cbc33..ea89328167 100644 --- a/bridge/foundation/ref_counted_internal.h +++ b/bridge/foundation/ref_counted_internal.h @@ -1,5 +1,6 @@ /* - * Copyright (C) 2022-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -79,8 +80,6 @@ class RefCountedThreadSafeBase { mutable bool adoption_required_; mutable bool destruction_started_; #endif - - DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafeBase); }; inline RefCountedThreadSafeBase::RefCountedThreadSafeBase() diff --git a/bridge/foundation/ref_counter.h b/bridge/foundation/ref_counter.h index ae8f77f980..f4626d2dfc 100644 --- a/bridge/foundation/ref_counter.h +++ b/bridge/foundation/ref_counter.h @@ -1,5 +1,6 @@ /* - * Copyright (C) 2022-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -118,8 +119,6 @@ class RefCountedThreadSafe : public internal::RefCountedThreadSafeBase { // and also writing one's own ref pointer class impossible. void Adopt() { internal::RefCountedThreadSafeBase::Adopt(); } #endif - - DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafe); }; // If you subclass |RefCountedThreadSafe| and want to keep your destructor diff --git a/bridge/foundation/ref_ptr.h b/bridge/foundation/ref_ptr.h index c99bacf799..e351bd5cbd 100644 --- a/bridge/foundation/ref_ptr.h +++ b/bridge/foundation/ref_ptr.h @@ -1,5 +1,6 @@ /* - * Copyright (C) 2022-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be @@ -16,6 +17,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 b5be8004de..d1a5a3d675 100644 --- a/bridge/foundation/ref_ptr_internal.h +++ b/bridge/foundation/ref_ptr_internal.h @@ -1,5 +1,6 @@ /* - * Copyright (C) 2022-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be diff --git a/bridge/foundation/string_view.cc b/bridge/foundation/string_view.cc new file mode 100644 index 0000000000..1de4a0b735 --- /dev/null +++ b/bridge/foundation/string_view.cc @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#include "string_view.h" + +namespace webf { + +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 webf diff --git a/bridge/foundation/string_view.h b/bridge/foundation/string_view.h new file mode 100644 index 0000000000..119a927eee --- /dev/null +++ b/bridge/foundation/string_view.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +#ifndef BRIDGE_FOUNDATION_STRING_VIEW_H_ +#define BRIDGE_FOUNDATION_STRING_VIEW_H_ + +#include <string> +#include "ascii_types.h" +#include "native_string.h" + +namespace webf { + +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 webf::IsLowerASCII(Characters8(), length()); + } + return webf::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 webf + +#endif // BRIDGE_FOUNDATION_STRING_VIEW_H_ diff --git a/bridge/foundation/task_queue.cc b/bridge/foundation/task_queue.cc index 084dc7b636..c95d0c4095 100644 --- a/bridge/foundation/task_queue.cc +++ b/bridge/foundation/task_queue.cc @@ -5,7 +5,7 @@ #include "task_queue.h" -namespace foundation { +namespace webf { int32_t TaskQueue::registerTask(const Task& task, void* data) { std::lock_guard<std::mutex> guard(queue_mutex_); @@ -32,4 +32,4 @@ void TaskQueue::flushTask() { m_map.clear(); } -} // namespace foundation +} // namespace webf diff --git a/bridge/foundation/task_queue.h b/bridge/foundation/task_queue.h index 54d2ef065d..56c758a0be 100644 --- a/bridge/foundation/task_queue.h +++ b/bridge/foundation/task_queue.h @@ -8,11 +8,12 @@ #include <mutex> #include <unordered_map> -#include "closure.h" #include "ref_counter.h" #include "ref_ptr.h" -namespace foundation { +namespace webf { + +using Task = void (*)(void*); class TaskQueue : public fml::RefCountedThreadSafe<TaskQueue> { public: @@ -35,6 +36,6 @@ class TaskQueue : public fml::RefCountedThreadSafe<TaskQueue> { FML_FRIEND_REF_COUNTED_THREAD_SAFE(TaskQueue); }; -} // namespace foundation +} // namespace webf #endif // BRIDGE_TASK_QUEUE_H diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index f38b67d7b4..6bd6f9cbad 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -1,75 +1,91 @@ /* - * Copyright (C) 2020-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "ui_command_buffer.h" -#include "dart_methods.h" +#include "core/dart_methods.h" +#include "core/executing_context.h" +#include "foundation/logging.h" #include "include/webf_bridge.h" -namespace foundation { +namespace webf { -UICommandBuffer::UICommandBuffer(int32_t contextId) : contextId(contextId) {} +UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context) {} -void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr, bool batchedUpdate) { - if (batchedUpdate) { - webf::getDartMethod()->requestBatchUpdate(contextId); - update_batched = true; +UICommandBuffer::~UICommandBuffer() { +#if FLUTTER_BACKEND + // Flush and execute all disposeEventTarget commands when context released. + if (context_->dartMethodPtr()->flushUICommand != nullptr && !isDartHotRestart()) { + context_->dartMethodPtr()->flushUICommand(context_->contextId()); } +#endif +} - UICommandItem item{id, type, nativePtr}; - queue.emplace_back(item); +void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr) { + UICommandItem item{id, static_cast<int32_t>(type), nativePtr}; + addCommand(item); } -void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr) { - if (!update_batched) { -#if FLUTTER_BACKEND - webf::getDartMethod()->requestBatchUpdate(contextId); -#endif - update_batched = true; - } +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}; + addCommand(item); +} - UICommandItem item{id, type, nativePtr}; - queue.emplace_back(item); +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}; + addCommand(item); } -void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01, void* nativePtr) { - if (!update_batched) { -#if FLUTTER_BACKEND - webf::getDartMethod()->requestBatchUpdate(contextId); - update_batched = true; -#endif +void UICommandBuffer::addCommand(const UICommandItem& item) { + if (size_ >= MAXIMUM_UI_COMMAND_SIZE) { + if (UNLIKELY(isDartHotRestart())) { + clear(); + } else { + context_->FlushUICommand(); + } + assert(size_ == 0); } - UICommandItem item{id, type, args_01, 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) { - webf::getDartMethod()->requestBatchUpdate(contextId); - update_batched = true; + if (UNLIKELY(!update_batched_ && context_->IsContextValid() && + context_->dartMethodPtr()->requestBatchUpdate != nullptr)) { + context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); + update_batched_ = true; } #endif - UICommandItem item{id, type, args_01, args_02, nativePtr}; - queue.emplace_back(item); + + buffer_[size_] = item; + size_++; } UICommandItem* UICommandBuffer::data() { - return queue.data(); + return buffer_; } int64_t UICommandBuffer::size() { - return queue.size(); + return size_; +} + +bool UICommandBuffer::empty() { + return size_ == 0; } void UICommandBuffer::clear() { - for (auto command : queue) { - delete[] reinterpret_cast<const uint16_t*>(command.string_01); - delete[] reinterpret_cast<const uint16_t*>(command.string_02); + for (int i = 0; i < size_; i++) { + delete[] reinterpret_cast<const uint16_t*>(buffer_[i].string_01); + delete[] reinterpret_cast<const uint16_t*>(buffer_[i].string_02); } - queue.clear(); - update_batched = false; + size_ = 0; + memset(buffer_, 0, sizeof(buffer_)); + update_batched_ = false; } -} // namespace foundation +} // namespace webf diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index c365571647..8c03c70735 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -1,32 +1,95 @@ /* - * Copyright (C) 2020-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #ifndef BRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ #define BRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ -#include "include/webf_bridge.h" +#include <cinttypes> +#include "bindings/qjs/native_string_utils.h" +#include "native_value.h" -namespace foundation { +namespace webf { + +class ExecutingContext; + +enum class UICommand { + kCreateElement, + kCreateTextNode, + kCreateComment, + kCreateDocument, + kCreateWindow, + kDisposeEventTarget, + kAddEvent, + kRemoveNode, + kInsertAdjacentNode, + kSetStyle, + kSetAttribute, + kRemoveAttribute, + kCloneNode, + kRemoveEvent, + kCreateDocumentFragment, + kCreatePerformance, +}; + +#define MAXIMUM_UI_COMMAND_SIZE 2048 + +struct UICommandItem { + UICommandItem() = default; + 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{0}; + int32_t id{0}; + 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}; +}; + +bool isDartHotRestart(); 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); + ~UICommandBuffer(); + 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(); + bool empty(); void clear(); private: - int32_t contextId; - std::atomic<bool> update_batched{false}; - std::vector<UICommandItem> queue; + void addCommand(const UICommandItem& item); + + ExecutingContext* context_{nullptr}; + UICommandItem buffer_[MAXIMUM_UI_COMMAND_SIZE]; + bool update_batched_{false}; + int64_t size_{0}; }; -} // namespace foundation +} // namespace webf #endif // BRIDGE_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 9013d5e9bf..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 "webf_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 23e967be84..1de9e5af55 100644 --- a/bridge/foundation/ui_task_queue.cc +++ b/bridge/foundation/ui_task_queue.cc @@ -5,14 +5,13 @@ #include "ui_task_queue.h" -namespace foundation { +namespace webf { 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 webf diff --git a/bridge/foundation/ui_task_queue.h b/bridge/foundation/ui_task_queue.h index 4a1764a941..793e0d28cd 100644 --- a/bridge/foundation/ui_task_queue.h +++ b/bridge/foundation/ui_task_queue.h @@ -1,5 +1,6 @@ /* - * Copyright (C) 2019-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #ifndef BRIDGE_UI_TASK_QUEUE_H @@ -7,9 +8,7 @@ #include "task_queue.h" -namespace foundation { - -using Task = void (*)(void*); +namespace webf { class UITaskQueue : public TaskQueue { public: @@ -29,6 +28,6 @@ class UITaskQueue : public TaskQueue { int m_contextId; }; -} // namespace foundation +} // namespace webf #endif // BRIDGE_UI_TASK_QUEUE_H diff --git a/bridge/include/webf_bridge.h b/bridge/include/webf_bridge.h index 9793a47aca..dd355c5a7d 100644 --- a/bridge/include/webf_bridge.h +++ b/bridge/include/webf_bridge.h @@ -1,34 +1,20 @@ /* - * Copyright (C) 2019-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #ifndef WEBF_BRIDGE_EXPORT_H #define WEBF_BRIDGE_EXPORT_H -#include <cstdint> #include <thread> -#include "dart_methods.h" -#include "webf_foundation.h" - #define WEBF_EXPORT_C extern "C" __attribute__((visibility("default"))) __attribute__((used)) #define WEBF_EXPORT __attribute__((__visibility__("default"))) -WEBF_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 NativeValue NativeValue; +typedef struct NativeScreen NativeScreen; +typedef struct NativeByteCode NativeByteCode; struct WebFInfo; @@ -39,75 +25,33 @@ struct WebFInfo { 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 WEBF_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); WEBF_EXPORT_C -void initJSPagePool(int poolSize); +void initJSPagePool(int poolSize, uint64_t* dart_methods, int32_t dart_methods_len); WEBF_EXPORT_C void disposePage(int32_t contextId); WEBF_EXPORT_C -int32_t allocateNewPage(int32_t targetContextId); +int32_t allocateNewPage(int32_t targetContextId, uint64_t* dart_methods, int32_t dart_methods_len); WEBF_EXPORT_C void* getPage(int32_t contextId); bool checkPage(int32_t contextId); bool checkPage(int32_t contextId, void* context); WEBF_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); WEBF_EXPORT_C void evaluateQuickjsByteCode(int32_t contextId, uint8_t* bytes, int32_t byteLen); WEBF_EXPORT_C void parseHTML(int32_t contextId, const char* code, int32_t length); WEBF_EXPORT_C -void reloadJsContext(int32_t contextId); -WEBF_EXPORT_C -void invokeModuleEvent(int32_t contextId, NativeString* module, const char* eventType, void* event, NativeString* extra); +void reloadJsContext(int32_t contextId, uint64_t* dart_methods, int32_t dart_methods_len); WEBF_EXPORT_C -void registerDartMethods(uint64_t* methodBytes, int32_t length); -WEBF_EXPORT_C -NativeScreen* createScreen(double width, double height); +NativeValue* invokeModuleEvent(int32_t contextId, + NativeString* module, + const char* eventType, + void* event, + NativeValue* extra); WEBF_EXPORT_C WebFInfo* getWebFInfo(); WEBF_EXPORT_C @@ -117,9 +61,7 @@ void flushUITask(int32_t contextId); WEBF_EXPORT_C void registerUITask(int32_t contextId, Task task, void* data); WEBF_EXPORT_C -void flushUICommandCallback(); -WEBF_EXPORT_C -UICommandItem* getUICommandItems(int32_t contextId); +void* getUICommandItems(int32_t contextId); WEBF_EXPORT_C int64_t getUICommandItemSize(int32_t contextId); WEBF_EXPORT_C diff --git a/bridge/include/webf_bridge_test.h b/bridge/include/webf_bridge_test.h index 55eb6528f8..03bcee26dc 100644 --- a/bridge/include/webf_bridge_test.h +++ b/bridge/include/webf_bridge_test.h @@ -1,24 +1,24 @@ /* - * Copyright (C) 2019-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #ifndef WEBF_BRIDGE_TEST_EXPORT_H #define WEBF_BRIDGE_TEST_EXPORT_H -#include <cstdint> #include "webf_bridge.h" WEBF_EXPORT_C void initTestFramework(int32_t contextId); WEBF_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); WEBF_EXPORT_C void executeTest(int32_t contextId, ExecuteCallback executeCallback); WEBF_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/webf_foundation.h b/bridge/include/webf_foundation.h deleted file mode 100644 index d5293a0fd5..0000000000 --- a/bridge/include/webf_foundation.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2020 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef BRIDGE_FOUNDATION_H -#define BRIDGE_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 WEBF_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 DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete - -#define DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete - -#define DISALLOW_MOVE(TypeName) \ - TypeName(TypeName&&) = delete; \ - TypeName& operator=(TypeName&&) = delete - -#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&) = delete; \ - TypeName& operator=(const TypeName&) = delete - -#define DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) \ - TypeName(const TypeName&) = delete; \ - TypeName(TypeName&&) = delete; \ - TypeName& operator=(const TypeName&) = delete; \ - TypeName& operator=(TypeName&&) = delete - -#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ - TypeName() = delete; \ - 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 WEBF_EXPORT UICommandCallbackQueue* instance(); - WEBF_EXPORT void registerCallback(const Callback& callback, void* data); - WEBF_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/page.cc b/bridge/page.cc deleted file mode 100644 index 152a2c7062..0000000000 --- a/bridge/page.cc +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF 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 webf { - -using namespace binding::qjs; - -std::unordered_map<std::string, NativeByteCode> WebFPage::pluginByteCode{}; -ConsoleMessageHandler WebFPage::consoleMessageHandler{nullptr}; - -webf::WebFPage** WebFPage::pageContextPool{nullptr}; - -WebFPage::WebFPage(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 - - initWebFPolyFill(this); - - for (auto& p : pluginByteCode) { - evaluateByteCode(p.second.bytes, p.second.length); - } - -#if ENABLE_PROFILE - nativePerformance.mark(PERF_JS_POLYFILL_INIT_END); -#endif -} - -bool WebFPage::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 WebFPage::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 WebFPage::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 WebFPage::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 WebFPage::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* WebFPage::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 WebFPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { - if (!m_context->isValid()) - return; - m_context->evaluateByteCode(bytes, byteLength); -} - -WebFPage::~WebFPage() { -#if IS_TEST - if (disposeCallback != nullptr) { - disposeCallback(this); - } -#endif - delete m_context; - WebFPage::pageContextPool[contextId] = nullptr; -} - -void WebFPage::reportError(const char* errmsg) { - m_handler(m_context->getContextId(), errmsg); -} - -} // namespace webf diff --git a/bridge/page_test.cc b/bridge/page_test.cc deleted file mode 100644 index f3a85a1de5..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 webf { - -bool WebFPageTest::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 WebFPageTest::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<WebFPage*>(context->getOwner()); - auto bridgeTest = static_cast<WebFPageTest*>(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 '__webf_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); - } - auto blob = static_cast<webf::binding::qjs::BlobInstance*>(JS_GetOpaque(blobValue, webf::binding::qjs::Blob::kBlobClassID)); - - if (blob == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__webf_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); - } - - if (!JS_IsString(screenShotValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__webf_match_image_snapshot__': parameter 2 (match) must be an string."); - } - - if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__webf_match_image_snapshot__': parameter 3 (callback) is not an function."); - } - - if (!JS_IsFunction(ctx, callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__webf_match_image_snapshot__': parameter 3 (callback) is not an function."); - } - - if (getDartMethod()->matchImageSnapshot == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__webf_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); - } - - std::unique_ptr<NativeString> screenShotNativeString = webf::binding::qjs::jsValueToNativeString(ctx, screenShotValue); - auto bridge = static_cast<WebFPageTest*>(static_cast<WebFPage*>(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 '__webf_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 '__webf_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 '__webf_simulate_pointer__': first arguments should be an array."); - } - - JSValue pointerValue = argv[1]; - if (!JS_IsNumber(pointerValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__webf_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 '__webf_simulate_keypress__': dart method(simulateInputText) is not registered."); - } - - JSValue& charStringValue = argv[0]; - - if (!JS_IsString(charStringValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__webf_simulate_keypress__': first arguments should be a string"); - } - - std::unique_ptr<NativeString> nativeString = webf::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; -} - -WebFPageTest::WebFPageTest(WebFPage* bridge) : m_page(bridge), m_page_context(bridge->getContext()) { - bridge->owner = this; - bridge->disposeCallback = [](WebFPage* bridge) { delete static_cast<WebFPageTest*>(bridge->owner); }; - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__webf_execute_test__", 1); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, matchImageSnapshot, "__webf_match_image_snapshot__", 3); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, environment, "__webf_environment__", 0); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulatePointer, "__webf_simulate_pointer__", 1); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulateInputText, "__webf_simulate_inputtext__", 1); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, triggerGlobalError, "__webf_trigger_global_error__", 0); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, parseHTML, "__webf_parse_html__", 1); - - initWebFTestFramework(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 WebFPageTest::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 = webf::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 webf diff --git a/bridge/page_test.h b/bridge/page_test.h deleted file mode 100644 index 38e8363451..0000000000 --- a/bridge/page_test.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2020-present The Kraken authors. All rights reserved. - */ - -#ifndef BRIDGE_PAGE_TEST_H -#define BRIDGE_PAGE_TEST_H - -#include "bindings/qjs/dom/document.h" -#include "bindings/qjs/html_parser.h" -#include "page.h" -#include "webf_bridge_test.h" - -namespace webf { - -struct ImageSnapShotContext { - JSValue callback; - binding::qjs::ExecutionContext* context; - list_head link; -}; - -class WebFPageTest final { - public: - explicit WebFPageTest() = delete; - explicit WebFPageTest(WebFPage* bridge); - - ~WebFPageTest() { - 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 - WebFPage* m_page; - /// the pointer of JSContext, overship belongs to JSContext - binding::qjs::ExecutionContext* m_page_context; -}; - -} // namespace webf - -#endif // BRIDGE_PAGE_TEST_H diff --git a/bridge/polyfill/scripts/js_to_c.js b/bridge/polyfill/scripts/js_to_c.js index a606299f08..88d1cce7a2 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 ${outputName.toUpperCase()}_H #define ${outputName.toUpperCase()}_H -#if WEBF_JSC_ENGINE -#include "bridge_jsc.h" -#elif WEBF_QUICK_JS_ENGINE -#include "page.h" -#endif +#include "core/executing_context.h" -void initWebF${outputName}(webf::WebFPage *page); +void initWebF${outputName}(webf::ExecutingContext *context); #endif // ${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) => `/* @@ -65,14 +61,14 @@ const getPolyFillSource = (source, outputName) => `/* ${getPolyFillJavaScriptSource(source)} -void initWebF${outputName}(webf::WebFPage *page) { +void initWebF${outputName}(webf::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 30da7c8b40..7bf669719c 100644 --- a/bridge/polyfill/src/bridge.ts +++ b/bridge/polyfill/src/bridge.ts @@ -3,11 +3,20 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -declare const __webf_invoke_module__: (module: string, method: string, params?: Object | null, fn?: (err: Error, data: any) => void) => string; +declare const __webf_invoke_module__: (module: string, method: string, params?: any | null, fn?: (err: Error, data: any) => any) => any; export const webfInvokeModule = __webf_invoke_module__; -declare const __webf_module_listener__: (fn: (moduleName: string, event: Event, extra: string) => void) => void; -export const addWebfModuleListener = __webf_module_listener__; +declare const __webf_add_module_listener__: (moduleName: string, fn: (event: Event, extra: any) => any) => void; +export const addWebfModuleListener = __webf_add_module_listener__; + +declare const __webf_clear_module_listener__: () => void; +export const clearWebfModuleListener = __webf_clear_module_listener__; + +declare const __webf_remove_module_listener__: (name: string) => void; +export const removeWebfModuleListener = __webf_remove_module_listener__; + +declare const __webf_location_reload__: () => void; +export const webfLocationReload = __webf_location_reload__; declare const __webf_print__: (log: string, level?: string) => void; -export const webfPrint = __webf_print__; +export const webfPrint = __webf_print__; \ No newline at end of file diff --git a/bridge/polyfill/src/dom.ts b/bridge/polyfill/src/dom.ts index f21734f7ed..bfd8b6d7d9 100644 --- a/bridge/polyfill/src/dom.ts +++ b/bridge/polyfill/src/dom.ts @@ -22,12 +22,3 @@ class SVGElement extends Element { Object.defineProperty(window, 'SVGElement', { value: SVGElement }); - -// Polyfill for document.getElementsByName -// https://html.spec.whatwg.org/multipage/dom.html#dom-document-getelementsbyname -Object.defineProperty(Object.getPrototypeOf(document), 'getElementsByName', { - configurable: true, - enumerable: true, - writable: true, - value: (elementName: string) => document.querySelectorAll(`[name="${elementName}"]`), -}); diff --git a/bridge/polyfill/src/events.ts b/bridge/polyfill/src/events.ts deleted file mode 100644 index 59e35d285c..0000000000 --- a/bridge/polyfill/src/events.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ - -const builtInWindowEvents = [ - "onclick", - "ondblclick", - "onload", - "onratechange", - "onresize", - "onscroll", - "onhashchange", - "onmessage", - "onpopstate", - "onrejectionhandled", - "onunhandledrejection", - "ontouchcancel", - "ontouchend", - "ontouchmove", - "ontouchstart" -]; - - -builtInWindowEvents.forEach(e => { - let pKey = '_' + e; - Object.defineProperty(window, e, { - get(): any { - return this[pKey]; - }, - set(value) { - if (this[pKey]) { - this.removeEventListener(e.substring(2), this[pKey]); - this[pKey] = null; - } - - this.addEventListener(e.substring(2), value); - this[pKey] = value; - } - }); -}); - -//@ts-ignore -export class ErrorEvent extends Event { - message?: string; - lineno?: number; - error?: Error; - colno?: number; - filename?: string; - - constructor(type: string, init?: ErrorEventInit) { - super(type); - if (init) { - this.message = init.message; - this.lineno = init.lineno; - this.error = init.error; - this.colno = init.colno; - this.filename = init.filename; - } - } -} - - -//@ts-ignore -export class PromiseRejectionEvent extends Event { - promise: Promise<any>; - reason: any; - - constructor(type: string, init?: PromiseRejectionEventInit) { - super(type); - - if (init) { - this.promise = init.promise; - this.reason = init.reason; - } - }; -} diff --git a/bridge/polyfill/src/global-event-handlers.ts b/bridge/polyfill/src/global-event-handlers.ts deleted file mode 100644 index 8342dba2cc..0000000000 --- a/bridge/polyfill/src/global-event-handlers.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ - -// IDL definitions for GlobalEventHandlers -// https://html.spec.whatwg.org/multipage/webappapis.html#idl-definitions - -const EVENT_PREFIX = 'on'; -const EVENT_ERROR = 'error'; -let _onErrorEventListener: EventListener | null; -let _onErrorEventHandler: OnErrorEventHandler; -export const GlobalEventHandlers = { - get onerror() : OnErrorEventHandler{ - return _onErrorEventHandler ?? null; - }, - set onerror(errorEventHandler: OnErrorEventHandler) { - _onErrorEventHandler = errorEventHandler; - - if (errorEventHandler) { - _onErrorEventListener = (event: ErrorEvent) => { - const error: Error = event.error; - errorEventHandler(event, error['sourceURL'] || location.href, error['line'] || 0, error['column'] || 0, error); - }; - addEventListener(EVENT_ERROR, _onErrorEventListener); - } else { - if (_onErrorEventListener) { - removeEventListener(EVENT_ERROR, _onErrorEventListener); - _onErrorEventListener = null; - } - } - }, -}; - -export const globalEvents = [ - EVENT_PREFIX + EVENT_ERROR, -]; - -export function registerGlobalEventHandlers(window: Window) { - // Register global event handlers for window. - globalEvents.forEach((key: string) => { - const propertyDecorator = Object.getOwnPropertyDescriptor(GlobalEventHandlers, key); - if (propertyDecorator) { - Object.defineProperty(window, key, propertyDecorator as PropertyDecorator); - } - }); -} diff --git a/bridge/polyfill/src/index.ts b/bridge/polyfill/src/index.ts index 1676c32f68..51b2c362d5 100644 --- a/bridge/polyfill/src/index.ts +++ b/bridge/polyfill/src/index.ts @@ -3,7 +3,6 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import 'es6-promise/dist/es6-promise.auto'; import './dom'; import { console } from './console'; import { fetch, Request, Response, Headers } from './fetch'; @@ -16,10 +15,7 @@ import { asyncStorage } from './async-storage'; import { URLSearchParams } from './url-search-params'; import { URL } from './url'; import { webf } from './webf'; -import { ErrorEvent, PromiseRejectionEvent } from './events'; -defineGlobalProperty('ErrorEvent', ErrorEvent); -defineGlobalProperty('PromiseRejectionEvent', PromiseRejectionEvent); defineGlobalProperty('console', console); defineGlobalProperty('Request', Request); defineGlobalProperty('Response', Response); @@ -34,7 +30,6 @@ defineGlobalProperty('asyncStorage', asyncStorage); defineGlobalProperty('URLSearchParams', URLSearchParams); defineGlobalProperty('URL', URL); defineGlobalProperty('webf', webf); -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 6ab99c2d36..8162c4152e 100644 --- a/bridge/polyfill/src/location.ts +++ b/bridge/polyfill/src/location.ts @@ -5,16 +5,14 @@ import { URL } from './url'; import { webf } from './webf'; +import { webfLocationReload } from './bridge'; -// @ts-ignore -const webfLocation = window.__location__; // Lazy parse url. let _url: URL; export function getUrl() : URL { return _url ? _url : (_url = new URL(location.href)); } -const bindReload = webfLocation.reload.bind(webfLocation); export const location = { get href() { return webf.invokeModule('Location', 'getHref'); @@ -53,7 +51,7 @@ export const location = { }; }, get reload() { - return bindReload; + return webfLocationReload.bind(this); }, get replace() { return (replaceURL: string) => { diff --git a/bridge/polyfill/src/method-channel.ts b/bridge/polyfill/src/method-channel.ts index 179623c855..0c48719b78 100644 --- a/bridge/polyfill/src/method-channel.ts +++ b/bridge/polyfill/src/method-channel.ts @@ -5,27 +5,23 @@ import { webfInvokeModule } from './bridge'; -type MethodCallHandler = (method: string, args: any[]) => void; +type MethodCallHandler = (args: any[]) => void; -let methodCallHandlers: MethodCallHandler[] = []; +let methodCallHandlers: {[key: string]: MethodCallHandler} = {}; // Like flutter platform channels export const methodChannel = { - setMethodCallHandler(handler: MethodCallHandler) { - console.warn('webf.methodChannel.setMethodCallHandler is a Deprecated API, use webf.methodChannel.addMethodCallHandler instead.'); - methodChannel.addMethodCallHandler(handler); - }, - addMethodCallHandler(handler: MethodCallHandler) { - methodCallHandlers.push(handler); - }, - removeMethodCallHandler(handler: MethodCallHandler) { - let index = methodCallHandlers.indexOf(handler); - if (index != -1) { - methodCallHandlers.splice(index, 1); + addMethodCallHandler(method: string, handler: MethodCallHandler) { + if (typeof handler !== 'function') { + throw new Error('webf.addMethodCallHandler: handler should be an function.'); } + methodCallHandlers[method] = handler; + }, + removeMethodCallHandler(method: string) { + delete methodCallHandlers[method]; }, clearMethodCallHandler() { - methodCallHandlers.length = 0; + methodCallHandlers = {}; }, invokeMethod(method: string, ...args: any[]): Promise<string> { return new Promise((resolve, reject) => { @@ -37,10 +33,10 @@ export const methodChannel = { }, }; -export function triggerMethodCallHandler(method: string, args: any) { - if (methodCallHandlers.length > 0) { - for (let handler of methodCallHandlers) { - handler(method, args); - } +export function triggerMethodCallHandler(method: string, args: any[]) { + if (!methodCallHandlers.hasOwnProperty(method)) { + return null; } + + return methodCallHandlers[method](args); } diff --git a/bridge/polyfill/src/test/index.js b/bridge/polyfill/src/test/index.js index 4c02f837ed..98ee83afbe 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 = __webf_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.WEBF_TEST_FILTER; - } -}); - let config = { oneFailurePerSpec: true, - failFast: environment.WEBF_STOP_ON_FAIL === 'true', - random: false, - specFilter: function (spec) { - return specFilter.matches(spec.getFullName()); - } + random: false }; env.configure(config); @@ -158,6 +135,7 @@ global.simulateInputText = __webf_simulate_inputtext__; function resetDocumentElement() { window.scrollTo(0, 0); + document.removeChild(document.documentElement); let html = document.createElement('html'); document.appendChild(html); diff --git a/bridge/polyfill/src/test/jasmine.js b/bridge/polyfill/src/test/jasmine.js index 22026f20f7..119e575f93 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/polyfill/src/webf.ts b/bridge/polyfill/src/webf.ts index 20d47d6e66..b6dc416c83 100644 --- a/bridge/polyfill/src/webf.ts +++ b/bridge/polyfill/src/webf.ts @@ -3,29 +3,17 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import { addWebfModuleListener, webfInvokeModule } from './bridge'; +import { addWebfModuleListener, webfInvokeModule, clearWebfModuleListener, removeWebfModuleListener } from './bridge'; import { methodChannel, triggerMethodCallHandler } from './method-channel'; import { dispatchConnectivityChangeEvent } from "./connection"; -function webfModuleListener(moduleName: string, event: Event, data: any) { - switch (moduleName) { - case 'Connection': { - dispatchConnectivityChangeEvent(event); - break; - } - case 'MethodChannel': { - const method = data[0]; - const args = data[1]; - triggerMethodCallHandler(method, args); - break; - } - } -} - -addWebfModuleListener(webfModuleListener); +addWebfModuleListener('Connection', (event, data) => dispatchConnectivityChangeEvent(event)); +addWebfModuleListener('MethodChannel', (event, data) => triggerMethodCallHandler(data[0], data[1])); export const webf = { methodChannel, invokeModule: webfInvokeModule, - addWebfModuleListener: addWebfModuleListener + addWebfModuleListener: addWebfModuleListener, + clearWebfModuleListener: clearWebfModuleListener, + removeWebfModuleListener: removeWebfModuleListener }; diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 9f6622051b..72400e3835 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -5,8 +5,13 @@ 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 { generatorSource } = require('../dist/idl/generator') +const { generateJSONTemplate } = require('../dist/json/generator'); +const { generateNamesInstaller } = require("../dist/json/generator"); program .version(packageJSON.version) @@ -25,24 +30,113 @@ 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); + // Analyze all files first. + for (let i = 0; i < blobs.length; i ++) { + let b = blobs[i]; + analyzer(b, definedPropertyCollector); + } + + for (let i = 0; i < blobs.length; i ++) { + let b = blobs[i]; + let result = generatorSource(b); + + 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, '../templates/json_templates') + }); - if (!fs.existsSync(b.dist)) { - fs.mkdirSync(b.dist); + 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, '../templates/json_templates'), template), filename); + }); + + for (let i = 0; i < blobs.length; i ++) { + let blob = blobs[i]; + blob.json.metadata.templates.forEach((targetTemplate) => { + if (targetTemplate.template === 'make_names') { + names_needs_install.add(targetTemplate.filename); + } + let depsBlob = {}; + if (targetTemplate.deps) { + let cwdDir = blob.source.split('/').slice(0, -1).join('/'); + targetTemplate.deps.forEach(depPath => { + let filename = depPath.split('/').slice(-1)[0].replace('.json5', ''); + depsBlob[filename] = new JSONBlob(path.join(cwdDir, depPath), filename).json; + }); + } + + // Inject allDefinedProperties set into the definedProperties source. + if (targetTemplate.filename === 'defined_properties') { + blob.json.data = Array.from(definedPropertyCollector.properties); + } + + if (targetTemplate.filename === 'defined_properties_initializer') { + blob.json.data = { + filenames: Array.from(definedPropertyCollector.files), + interfaces: Array.from(definedPropertyCollector.interfaces) + }; + } + + let targetTemplateHeaderData = templates.find(t => t.filename === targetTemplate.template + '.h'); + let targetTemplateBodyData = templates.find(t => t.filename === targetTemplate.template + '.cc'); + blob.filename = targetTemplate.filename; + let result = generateJSONTemplate(blobs[i], targetTemplateHeaderData, targetTemplateBodyData, depsBlob); + 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); + }); } - fs.writeFileSync(path.join(b.dist, b.filename) + '.h', result.header); - fs.writeFileSync(path.join(b.dist, b.filename) + '.cc', result.source); + // Generate name installer code. + let targetTemplateHeader = templates.find(t => t.filename === 'names_installer.h'); + let targetTemplateBody = templates.find(t => t.filename === 'names_installer.cc'); + let result = generateNamesInstaller(targetTemplateHeader, targetTemplateBody, names_needs_install); + let genFilePath = path.join(dist, 'names_installer'); + fs.writeFileSync(genFilePath + '.h', result.header); + result.source && fs.writeFileSync(genFilePath + '.cc', result.source); +} + +class DefinedPropertyCollector { + properties = new Set(); + files = new Set(); + interfaces = new Set(); } +let definedPropertyCollector = new DefinedPropertyCollector(); +let names_needs_install = new Set(); + +genCodeFromTypeDefine(); +genCodeFromJSONData(); diff --git a/bridge/scripts/code_generator/global.d.ts b/bridge/scripts/code_generator/global.d.ts new file mode 100644 index 0000000000..afa675a8a3 --- /dev/null +++ b/bridge/scripts/code_generator/global.d.ts @@ -0,0 +1,13 @@ +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; +type StaticMember<T> = T; diff --git a/bridge/scripts/code_generator/package.json b/bridge/scripts/code_generator/package.json index 47c80250fd..29cf3bce23 100644 --- a/bridge/scripts/code_generator/package.json +++ b/bridge/scripts/code_generator/package.json @@ -13,7 +13,8 @@ "@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" + "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 c149b18787..0000000000 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ /dev/null @@ -1,182 +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) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ - -#ifndef BRIDGE_${blob.filename.toUpperCase()}_H -#define BRIDGE_${blob.filename.toUpperCase()}_H - -#include "bindings/qjs/dom/element.h" - -namespace webf::binding::qjs { -${headers.join('')} -} - -#endif //BRIDGE_${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 c9c970e8e9..0000000000 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ /dev/null @@ -1,518 +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) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ - -#include "${blob.filename}.h" -#include "page.h" -#include "bindings/qjs/qjs_patch.h" - -namespace webf::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..0e599b1436 --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -0,0 +1,303 @@ +import ts, {HeritageClause, ScriptTarget, VariableStatement} from 'typescript'; +import {IDLBlob} from './IDLBlob'; +import { + ClassObject, + ClassObjectKind, + FunctionArguments, + FunctionArgumentType, + FunctionDeclaration, + FunctionObject, + IndexedPropertyDeclaration, + ParameterMode, + PropsDeclaration, +} from './declaration'; + +interface DefinedPropertyCollector { + properties: Set<string>; + files: Set<string>; + interfaces: Set<string>; +} + +export function analyzer(blob: IDLBlob, definedPropertyCollector: DefinedPropertyCollector) { + let code = blob.raw; + const sourceFile = ts.createSourceFile(blob.source, blob.raw, ScriptTarget.ES2020); + blob.objects = sourceFile.statements.map(statement => walkProgram(blob, statement, definedPropertyCollector)).filter(o => { + return o instanceof ClassObject || o instanceof FunctionObject; + }) as (FunctionObject | ClassObject)[]; +} + +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 getMixins(hertage: HeritageClause): string[] | null { + if (hertage.types.length <= 1) return null; + let mixins: string[] = []; + hertage.types.slice(1).forEach(types => { + let expression = types.expression; + if (expression.kind === ts.SyntaxKind.Identifier) { + mixins.push((expression as ts.Identifier).escapedText! as string); + } + }); + return mixins; +} + +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 === 'Promise') { + return FunctionArgumentType.promise; + }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); + } else if (identifier === 'StaticMember') { + if (mode) mode.static = 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); + let typeMode = new ParameterMode(); + args.type = getParameterType(parameter.type!, typeMode); + args.typeMode = typeMode; + 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(blob: IDLBlob, statement: ts.Statement, definedPropertyCollector: DefinedPropertyCollector) { + switch(statement.kind) { + case ts.SyntaxKind.InterfaceDeclaration: { + let interfaceName = getInterfaceName(statement) as string; + let s = (statement as ts.InterfaceDeclaration); + let obj = new ClassObject(); + let constructorDefined = false; + if (s.heritageClauses) { + let heritage = s.heritageClauses[0]; + let heritageType = getHeritageType(heritage); + let mixins = getMixins(heritage); + if (heritageType) obj.parent = heritageType.toString(); + if (mixins) obj.mixinParent = mixins; + } + + 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; + // @ts-ignore + } else if (decoratorExpression.expression.kind === ts.SyntaxKind.Identifier && decoratorExpression.expression.escapedText === 'Mixin') { + obj.kind = ClassObjectKind.mixin; + } + } + + if (obj.kind === ClassObjectKind.interface) { + definedPropertyCollector.interfaces.add('QJS' + interfaceName); + definedPropertyCollector.files.add(blob.filename); + } + + s.members.forEach(member => { + switch(member.kind) { + case ts.SyntaxKind.PropertySignature: { + let prop = new PropsDeclaration(); + let m = (member as ts.PropertySignature); + prop.name = getPropName(m.name); + prop.readonly = isParamsReadOnly(m); + + if (obj.kind === ClassObjectKind.interface) { + definedPropertyCollector.properties.add(prop.name); + } + + let propKind = m.type; + if (propKind) { + let mode = new ParameterMode(); + prop.type = getParameterType(propKind, mode); + prop.typeMode = mode; + if (member.questionToken) { + prop.optional = true; + } + 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; + constructorDefined = true; + break; + } + } + }); + + if (!constructorDefined && obj.kind === ClassObjectKind.interface) { + throw new Error(`Interface: ${interfaceName} didn't have constructor defined.`); + } + + ClassObject.globalClassMap[interfaceName] = obj; + + 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..6173af9b27 --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/declaration.ts @@ -0,0 +1,71 @@ +import {ParameterType} from "./analyzer"; + +export enum FunctionArgumentType { + // Basic types + dom_string, + object, + promise, + int32, + int64, + double, + boolean, + function, + void, + any, + null, + undefined, + array, +} + +export class FunctionArguments { + name: string; + type: ParameterType[] = []; + typeMode: ParameterMode; + required: boolean; +} + +export class ParameterMode { + newObject?: boolean; + dartImpl?: boolean; + static?: boolean; +} + +export class PropsDeclaration { + type: ParameterType[] = []; + typeMode: ParameterMode; + name: string; + readonly: boolean; + optional: 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, + mixin +} + +export class ClassObject { + static globalClassMap = new Map<string, ClassObject>(); + name: string; + parent: string; + mixinParent: 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..6c418998e7 --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/generateHeader.ts @@ -0,0 +1,95 @@ +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 {generateCoreTypeValue, generateRawTypeValue} 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; + } else if(object.kind === ClassObjectKind.mixin) { + return TemplateKind.null; + } + return TemplateKind.Interface; + } + return TemplateKind.null; +} + +function readTemplate(name: string) { + return fs.readFileSync(path.join(__dirname, '../../templates/idl_templates/' + name + '.h.tpl'), {encoding: 'utf-8'}); +} + +export function generateCppHeader(blob: IDLBlob, options: GenerateOptions) { + const baseTemplate = fs.readFileSync(path.join(__dirname, '../../templates/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) { + object = (object as ClassObject); + headerOptions.interface = true; + return _.template(readTemplate('interface'))({ + className: getClassName(blob), + parentClassName: object.parent, + blob: blob, + object, + generateRawTypeValue, + ...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, + generateCoreTypeValue + }); + } + 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..9d8b9eccff --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -0,0 +1,559 @@ +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"; + +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 isTypeNeedAllocate(type: ParameterType[]) { + switch(type[0]) { + case FunctionArgumentType.undefined: + case FunctionArgumentType.null: + case FunctionArgumentType.int32: + case FunctionArgumentType.int64: + case FunctionArgumentType.boolean: + case FunctionArgumentType.double: + return false; + default: + return true; + } +} + +export function generateCoreTypeValue(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 generateRawTypeValue(type: ParameterType[], is32Bit: boolean = false): string { + switch (type[0]) { + case FunctionArgumentType.int64: { + return 'int64_t'; + } + case FunctionArgumentType.int32: { + return 'int64_t'; + } + case FunctionArgumentType.double: { + return 'double'; + } + case FunctionArgumentType.boolean: { + return 'int64_t'; + } + case FunctionArgumentType.dom_string: { + if (is32Bit) { + return 'int64_t'; + } + + return 'NativeString*'; + } + default: + if (is32Bit) { + return 'int64_t'; + } + return 'void*'; + } + + if (typeof type[0] == 'string') { + if (is32Bit) { + return 'int64_t'; + } + return 'NativeBindingObject*'; + } + + return ''; +} + +export function generateIDLTypeConverter(type: ParameterType[], isOptional?: boolean): string { + let haveNull = type.some(t => t === FunctionArgumentType.null); + let returnValue = ''; + + if (type[0] === FunctionArgumentType.array) { + returnValue = `IDLSequence<${generateIDLTypeConverter(type.slice(1), isOptional)}>`; + } 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; + case FunctionArgumentType.promise: + returnValue = 'IDLPromise'; + break; + default: + case FunctionArgumentType.any: + returnValue = `IDLAny`; + break; + } + } + + if (haveNull) { + returnValue = `IDLNullable<${returnValue}>`; + } else if (isOptional) { + returnValue = `IDLOptional<${returnValue}>`; + } + + return returnValue; +} + +function generateNativeValueTypeConverter(type: ParameterType[]): string { + let returnValue = ''; + + if (typeof type[0] === 'string') { + return `NativeTypePointer<${type[0]}>`; + } + + 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 generateDartImplCallCode(blob: IDLBlob, declare: FunctionDeclaration, args: FunctionArguments[]): string { + let nativeArguments = args.map(i => { + return `NativeValueConverter<${generateNativeValueTypeConverter(i.type)}>::ToNativeValue(args_${i.name})`; + }); + + let returnValueAssignment = ''; + + if (declare.returnType[0] != FunctionArgumentType.void) { + returnValueAssignment = 'auto&& native_value ='; + } + + return ` +auto* self = toScriptWrappable<${getClassName(blob)}>(JS_IsUndefined(this_val) ? context->Global() : this_val); +NativeValue arguments[] = { + ${nativeArguments.join(',\n')} +}; +${returnValueAssignment}self->InvokeBindingMethod(binding_call_methods::k${declare.name}, ${nativeArguments.length}, arguments, exception_state); +${returnValueAssignment.length > 0 ? `return Converter<${generateIDLTypeConverter(declare.returnType)}>::ToValue(NativeValueConverter<${generateNativeValueTypeConverter(declare.returnType)}>::FromNativeValue(native_value))` : ''}; + `.trim(); +} + +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 (declare.returnTypeMode?.dartImpl) { + call = generateDartImplCallCode(blob, declare, declare.args.slice(0, argsIndex + 1)); + } else 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 +}) { + if (options.isConstructor && declaration.returnType[0] == FunctionArgumentType.void) { + return 'return JS_ThrowTypeError(ctx, "Illegal constructor");'; + } + + 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 (declaration.returnTypeMode?.dartImpl) { + call = generateDartImplCallCode(blob, declaration, declaration.args.slice(0, minimalRequiredArgc)); + } else if (options.isInstanceMethod) { + call = `auto* self = toScriptWrappable<${getClassName(blob)}>(JS_IsUndefined(this_val) ? context->Global() : this_val); +${returnValueAssignment} self->${generateCallMethodName(declaration.name)}(${minimalRequiredArgc > 0 ? `${requiredArguments.join(',')}` : 'exception_state'});`; + } else { + call = `${returnValueAssignment} ${getClassName(blob)}::${generateCallMethodName(declaration.name)}(context, ${requiredArguments.join(',')});`; + } + + let minimalRequiredCall = declaration.args.length == 0 ? call : `if (argc <= ${minimalRequiredArgc}) { + ${call} + break; +}`; + + return `${requiredArgumentsInit.join('\n')} +${minimalRequiredCall} + +${optionalArgumentsInit.join('\n')} +`; +} + +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}()`; + } + + return `Converter<${generateIDLTypeConverter(type)}>::ToValue(ctx, std::move(return_value))`; +} + +type GenFunctionBodyOptions = { isConstructor?: boolean, isInstanceMethod?: boolean }; + +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); + + let constructorPrototypeInit = (options.isConstructor && returnValueInit.length > 0) ? `JSValue proto = JS_GetPropertyStr(ctx, this_val, "prototype"); + JS_SetPrototype(ctx, return_value->ToQuickJSUnsafe(), proto); + JS_FreeValue(ctx, proto);` : ''; + + 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(); + } + ${constructorPrototypeInit} + return ${returnValueResult}; +`; +} + +function readTemplate(name: string) { + return fs.readFileSync(path.join(__dirname, '../../templates/idl_templates/' + name + '.cc.tpl'), {encoding: 'utf-8'}); +} + +export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { + const baseTemplate = fs.readFileSync(path.join(__dirname, '../../templates/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; + + function addObjectProps(prop: PropsDeclaration) { + options.classMethodsInstallList.push(`{"${prop.name}", ${prop.name}AttributeGetCallback, ${prop.readonly ? 'nullptr' : `${prop.name}AttributeSetCallback`}}`) + } + function addObjectMethods(method: FunctionDeclaration, i: number) { + 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}}`) + } + } + + object.props.forEach(addObjectProps); + + let overloadMethods = {}; + let filtedMethods: FunctionDeclaration[] = []; + object.methods.forEach(addObjectMethods); + + 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('nullptr'); + } else { + wrapperTypeRegisterList.push('nullptr'); + wrapperTypeRegisterList.push('nullptr'); + + wrapperTypeRegisterList.push(`StringPropertyGetterCallback`); + if (!object.indexedProp.readonly) { + wrapperTypeRegisterList.push(`StringPropertySetterCallback`); + } else { + wrapperTypeRegisterList.push('nullptr'); + } + } + + wrapperTypeRegisterList.push('PropertyCheckerCallback'); + wrapperTypeRegisterList.push('PropertyEnumerateCallback'); + } + + let mixinParent = object.mixinParent; + let mixinObjects: ClassObject[] | null = null; + if (mixinParent) { + mixinObjects = mixinParent.map(mixinName => ClassObject.globalClassMap[mixinName]).filter(o => !!o); + + mixinObjects.forEach(mixinObject => { + mixinObject.methods.forEach(addObjectMethods); + mixinObject.props.forEach(addObjectProps); + }); + } + + 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, + mixinObjects, + generateFunctionBody, + generateCoreTypeValue, + generateRawTypeValue, + generateOverLoadSwitchBody, + isTypeNeedAllocate, + 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..f361783585 --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/utils.ts @@ -0,0 +1,34 @@ +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); + } + if (raw.slice(0, 2) == 'ui') { + return 'UI' + raw.slice(2); + } + + 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..f682ddbd8b --- /dev/null +++ b/bridge/scripts/code_generator/src/json/generator.ts @@ -0,0 +1,67 @@ +import {JSONBlob} from './JSONBlob'; +import {JSONTemplate} from './JSONTemplate'; +import _ from 'lodash'; + +function generateHeader(blob: JSONBlob, template: JSONTemplate, deps?: JSONBlob[]): string { + let compiled = _.template(template.raw); + return compiled({ + _: _, + name: blob.filename, + template_path: blob.source, + data: blob.json.data, + deps, + upperCamelCase + }).split('\n').filter(str => { + return str.trim().length > 0; + }).join('\n'); +} + + +function upperCamelCase(name: string) { + return _.upperFirst(_.camelCase(name)); +} + +function generateBody(blob: JSONBlob, template: JSONTemplate, deps?: JSONBlob[]): string { + let compiled = _.template(template.raw); + return compiled({ + template_path: blob.source, + name: blob.filename, + data: blob.json.data, + deps, + upperCamelCase, + }).split('\n').filter(str => { + return str.trim().length > 0; + }).join('\n'); +} + +export function generateJSONTemplate(blob: JSONBlob, headerTemplate: JSONTemplate, bodyTemplate?: JSONTemplate, depsBlob?: JSONBlob[]) { + let header = generateHeader(blob, headerTemplate, depsBlob); + let body = bodyTemplate ? generateBody(blob, bodyTemplate, depsBlob) : ''; + + return { + header: header, + source: body, + }; +} + +function generateNames(template: JSONTemplate, names: Set<string>) { + let compiled = _.template(template.raw); + return compiled({ + _: _, + name: 'names_installer', + names: Array.from(names), + upperCamelCase + }).split('\n').filter(str => { + return str.trim().length > 0; + }).join('\n'); +} + +export function generateNamesInstaller(headerTemplate: JSONTemplate, bodyTemplate: JSONTemplate, names: Set<string>) { + let header = generateNames(headerTemplate, names); + let body = generateNames(bodyTemplate, names); + + 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/templates/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl new file mode 100644 index 0000000000..716b6c8404 --- /dev/null +++ b/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl @@ -0,0 +1,87 @@ +/* +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF 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" +#include "core/dom/element.h" +#include "core/dom/text.h" +#include "core/dom/document.h" +#include "core/dom/document_fragment.h" +#include "core/dom/comment.h" +#include "core/input/touch_list.h" +#include "core/html/html_all_collection.h" +#include "defined_properties.h" + +namespace webf { + +<% 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/templates/idl_templates/base.h.tpl b/bridge/scripts/code_generator/templates/idl_templates/base.h.tpl new file mode 100644 index 0000000000..3ebfd01baa --- /dev/null +++ b/bridge/scripts/code_generator/templates/idl_templates/base.h.tpl @@ -0,0 +1,15 @@ +/* +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ + +#ifndef BRIDGE_<%= blob.filename.toUpperCase() %>_H +#define BRIDGE_<%= blob.filename.toUpperCase() %>_H + +#include <quickjs/quickjs.h> +#include "bindings/qjs/wrapper_type_info.h" +#include "bindings/qjs/generated_code_helper.h" + +<%= content %> + +#endif //BRIDGE_<%= blob.filename.toUpperCase() %>T_H diff --git a/bridge/scripts/code_generator/templates/idl_templates/dictionary.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/dictionary.cc.tpl new file mode 100644 index 0000000000..7dfea7af16 --- /dev/null +++ b/bridge/scripts/code_generator/templates/idl_templates/dictionary.cc.tpl @@ -0,0 +1,63 @@ +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, prop.optional) %>>::ToValue(ctx, <%= prop.name %>_)); + <% }); %> + + return true; +} + +bool <%= className %>::FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + <% if (object.parent) { %> + <%= object.parent %>::FillMembersWithQJSObject(ctx, value, exception_state); + <% } %> + + if (!JS_IsObject(value)) { + return false; + } + + <% _.forEach(props, function(prop, index) { %> + + <% if (prop.optional) { %> + { + JSAtom key = JS_NewAtom(ctx, "<%= prop.name %>"); + if (JS_HasProperty(ctx, value, key)) { + JSValue v = JS_GetProperty(ctx, value, key); + <%= prop.name %>_ = Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::FromValue(ctx, v, exception_state); + JS_FreeValue(ctx, v); + has_<%= prop.name %>_ = true; + } + JS_FreeAtom(ctx, key); + } + <% } else { %> + { + JSValue v = JS_GetPropertyStr(ctx, value, "<%= prop.name %>"); + has_<%= prop.name %>_ = true; + <%= prop.name %>_ = Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::FromValue(ctx, v, exception_state); + JS_FreeValue(ctx, v); + } + <% } %> + + <% }); %> + + return true; +} diff --git a/bridge/scripts/code_generator/templates/idl_templates/dictionary.h.tpl b/bridge/scripts/code_generator/templates/idl_templates/dictionary.h.tpl new file mode 100644 index 0000000000..15fd3d6a3d --- /dev/null +++ b/bridge/scripts/code_generator/templates/idl_templates/dictionary.h.tpl @@ -0,0 +1,39 @@ + +<% if (object.parent) { %> +#include "qjs_<%= _.snakeCase(object.parent) %>.h" +<% } %> + +namespace webf { + +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) { %> + <%= generateCoreTypeValue(prop.type) %> <%= prop.name %>() const { + assert(has_<%= prop.name %>_); + return <%= prop.name %>_; + } + bool has<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>() const { return has_<%= prop.name %>_; } + void set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(<%= generateCoreTypeValue(prop.type) %> value) { + <%= prop.name %>_ = value; + has_<%= prop.name %>_ = true; + } + <% })); %> + bool FillQJSObjectWithMembers(JSContext *ctx, JSValue qjs_dictionary) const override; + bool FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state) override; +private: + <% _.forEach(props, (function(prop, index) { %> + <%= generateCoreTypeValue(prop.type) %> <%= prop.name %>_; + bool has_<%= prop.name %>_ = false; + <% })); %> +}; + +} diff --git a/bridge/scripts/code_generator/templates/idl_templates/global_function.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/global_function.cc.tpl new file mode 100644 index 0000000000..3abca6a798 --- /dev/null +++ b/bridge/scripts/code_generator/templates/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/templates/idl_templates/global_function.h.tpl b/bridge/scripts/code_generator/templates/idl_templates/global_function.h.tpl new file mode 100644 index 0000000000..4b38cfe531 --- /dev/null +++ b/bridge/scripts/code_generator/templates/idl_templates/global_function.h.tpl @@ -0,0 +1,14 @@ +#include "core/<%= blob.implement %>.h" + +namespace webf { + +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/templates/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl new file mode 100644 index 0000000000..85d96d0a1c --- /dev/null +++ b/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl @@ -0,0 +1,224 @@ +<% 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) { %> + bool QJS<%= className %>::PropertyCheckerCallback(JSContext* ctx, JSValueConst obj, JSAtom key) { + auto* self = toScriptWrappable<<%= className %>>(obj); + ExceptionState exception_state; + MemberMutationScope scope{ExecutingContext::From(ctx)}; + bool result = self->NamedPropertyQuery(AtomicString(ctx, key), exception_state); + if (UNLIKELY(exception_state.HasException())) { + return false; + } + return result; + } + int QJS<%= className %>::PropertyEnumerateCallback(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, JSValue obj) { + auto* self = toScriptWrappable<<%= className %>>(obj); + ExceptionState exception_state; + MemberMutationScope scope{ExecutingContext::From(ctx)}; + std::vector<AtomicString> props; + self->NamedPropertyEnumerator(props, exception_state); + auto *tabs = new JSPropertyEnum[props.size()]; + for(int i = 0; i < props.size(); i ++) { + tabs[i].atom = JS_DupAtom(ctx, props[i].Impl()); + tabs[i].is_enumerable = true; + } + + *plen = props.size(); + *ptab = tabs; + return 0; + } + + <% if (object.indexedProp.indexKeyType == 'number') { %> + JSValue QJS<%= className %>::IndexedPropertyGetterCallback(JSContext* ctx, JSValue obj, uint32_t index) { + ExceptionState exception_state; + MemberMutationScope scope{ExecutingContext::From(ctx)}; + auto* self = toScriptWrappable<<%= className %>>(obj); + if (index >= self->length()) { + return JS_UNDEFINED; + } + <%= generateCoreTypeValue(object.indexedProp.type) %> result = self->item(index, exception_state); + if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); + } + + return Converter<<%= generateIDLTypeConverter(object.indexedProp.type, object.indexedProp.optional) %>>::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)}; + ${generateCoreTypeValue(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, object.indexedProp.optional) %>>::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, object.indexedProp.optional) %>>::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, object.indexedProp.optional) %>>::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; + }; + <% } %> + <% } %> + <% } %> + + +static thread_local AttributeMap* internal_properties = nullptr; + +void QJS<%= className %>::InitAttributeMap() { + internal_properties = new AttributeMap(); + + for(int i = 0; i < <%= object.props.length %>; i ++) { + <% object.props.forEach(prop => { %> + internal_properties->emplace(std::make_pair(defined_properties::k<%= prop.name %>, true)); + <% }) %> + } +} + +void QJS<%= className %>::DisposeAttributeMap() { + delete internal_properties; +} + +AttributeMap* QJS<%= className %>::definedAttributeMap() { + assert(internal_properties != nullptr); + return internal_properties; +} + +bool QJS<%= className %>::IsAttributeDefinedInternal(const AtomicString& key) { + return definedAttributeMap()->count(key) > 0; +} + +<% _.forEach(filtedMethods, function(method, index) { %> + + <% if (overloadMethods[method.name] && overloadMethods[method.name].length > 1) { %> + <% _.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; + auto&& native_value = <%= blob.filename %>->GetBindingProperty(binding_call_methods::k<%= prop.name %>, exception_state); + <% if (isTypeNeedAllocate(prop.type)) { %> + typename <%= generateNativeValueTypeConverter(prop.type) %>::ImplType v = NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::FromNativeValue(ctx, native_value); + <% } else { %> + typename <%= generateNativeValueTypeConverter(prop.type) %>::ImplType v = NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::FromNativeValue(native_value); + <% } %> + if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); + } + return Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::ToValue(ctx, v); + <% } else if (prop.typeMode && prop.typeMode.static) { %> + return Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::ToValue(ctx, <%= className %>::<%= prop.name %>); + <% } else { %> + return Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::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, prop.optional) %>>::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]); +} +<% } %> +<% }); %> + + +<% if (mixinObjects) { %> +<% mixinObjects.forEach(function(object) { %> + +<% _.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)}; + return Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::ToValue(ctx, <%= object.name %>::<%= prop.name %>(*<%= blob.filename %>)); +} +<% if (!prop.readonly) { %> +static JSValue <%= prop.name %>AttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* <%= blob.filename %> = toScriptWrappable<<%= className %>>(this_val); + ExceptionState exception_state; + auto&& v = Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::FromValue(ctx, argv[0], exception_state); + if (exception_state.HasException()) { + return exception_state.ToQuickJS(); + } + MemberMutationScope scope{ExecutingContext::From(ctx)}; + + <%= object.name %>::set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(*<%= blob.filename %>, v, exception_state); + if (exception_state.HasException()) { + return exception_state.ToQuickJS(); + } + + return JS_DupValue(ctx, argv[0]); +} +<% } %> +<% }); %> + + +<% }); %> +<% } %> diff --git a/bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl b/bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl new file mode 100644 index 0000000000..349d61a50f --- /dev/null +++ b/bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl @@ -0,0 +1,76 @@ +#include "core/<%= blob.implement %>.h" + +<% if(parentClassName) { %> +#include "qjs_<%= _.snakeCase(parentClassName) %>.h" +<% } %> + +namespace webf { + +class ExecutingContext; + +<% if (className != "Event" && className != "CustomEvent" && _.endsWith(className, "Event")){ %> + +// 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 Native<%= className %> { + Native<%= parentClassName %> native_event; + <% _.forEach(object.props, function(prop, index) { %> + <% if (prop.typeMode.static) { return; } %> +<%= generateRawTypeValue(prop.type, true) %> <%= prop.name %>; + <% }) %> +}; +#else +// Use pointer instead of int64_t on 64 bit system can help compiler to choose best register for better running +// performance. +struct Native<%= className %> { +Native<%= parentClassName %> native_event; +<% _.forEach(object.props, function(prop, index) { %> +<% if (prop.typeMode.static) { return; } %> +<%= generateRawTypeValue(prop.type) %> <%= prop.name %>; +<% }) %> +}; +#endif +<% } %> + +using AttributeMap = std::unordered_map<AtomicString, bool, AtomicString::KeyHasher>; + +class QJS<%= className %> : public QJSInterfaceBridge<QJS<%= className %>, <%= className%>> { + public: + static void Install(ExecutingContext* context); + static void InitAttributeMap(); + static void DisposeAttributeMap(); + static AttributeMap* definedAttributeMap(); + static bool IsAttributeDefinedInternal(const AtomicString& key); + static WrapperTypeInfo* GetWrapperTypeInfo() { + return const_cast<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) { %> + static int PropertyEnumerateCallback(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, JSValueConst obj); + static bool PropertyCheckerCallback(JSContext* ctx, JSValueConst obj, JSAtom atom); + <% 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/templates/json_templates/defined_properties_initializer.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.cc.tpl new file mode 100644 index 0000000000..6d8d1c740a --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.cc.tpl @@ -0,0 +1,27 @@ +// Generated from template: +// code_generator/src/json/templates/defined_properties_initializer.cc.tpl +// and input files: +// <%= template_path %> + +#include "defined_properties_initializer.h" + +<% data.filenames.forEach(filename => { %> +#include "<%= filename %>.h" +<% }) %> + +namespace webf { + +void DefinedPropertiesInitializer::Init() { + <% data.interfaces.forEach(interfaceName => { %> + <%= interfaceName %>::InitAttributeMap(); + <% }) %> +} + +void DefinedPropertiesInitializer::Dispose() { + <% data.interfaces.forEach(interfaceName => { %> + <%= interfaceName %>::DisposeAttributeMap(); + <% }) %> +} + + +} \ No newline at end of file diff --git a/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.h.tpl b/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.h.tpl new file mode 100644 index 0000000000..77da466fa5 --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.h.tpl @@ -0,0 +1,22 @@ +/* +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ + +#ifndef BRIDGE_DEFINED_PROPERTIES_INTIALIZER_H_ +#define BRIDGE_DEFINED_PROPERTIES_INTIALIZER_H_ + +#include "bindings/qjs/atomic_string.h" + +namespace webf { + +class DefinedPropertiesInitializer { + public: + static void Init(); + static void Dispose(); +}; + + +} // namespace webf + +#endif // BRIDGE_DEFINED_PROPERTIES_INTIALIZER_H_ diff --git a/bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl new file mode 100644 index 0000000000..01ffa2f70d --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl @@ -0,0 +1,102 @@ +/* +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF 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 webf { + +using HTMLConstructorFunction = HTMLElement* (*)(Document&); + +using HTMLFunctionMap = std::unordered_map<AtomicString, HTMLConstructorFunction, AtomicString::KeyHasher>; + +static thread_local 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 webf diff --git a/bridge/scripts/code_generator/templates/json_templates/element_factory.h.tpl b/bridge/scripts/code_generator/templates/json_templates/element_factory.h.tpl new file mode 100644 index 0000000000..ce0703e4a8 --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/element_factory.h.tpl @@ -0,0 +1,25 @@ +/* +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ + +#ifndef BRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ +#define BRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ + +#include "bindings/qjs/atomic_string.h" + +namespace webf { + +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 webf + +#endif // BRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ diff --git a/bridge/scripts/code_generator/templates/json_templates/element_type_helper.h.tpl b/bridge/scripts/code_generator/templates/json_templates/element_type_helper.h.tpl new file mode 100644 index 0000000000..a5e248765c --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/element_type_helper.h.tpl @@ -0,0 +1,62 @@ + // Generated from template: + // code_generator/src/json/templates/element_type_helper.h.tpl + // and input files: + // <%= template_path %> + +#ifndef BRIDGE_CORE_HTML_TYPE_HELPER_H_ +#define BRIDGE_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 webf { + + +<% 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/templates/json_templates/event_factory.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl new file mode 100644 index 0000000000..ebbce63c03 --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl @@ -0,0 +1,109 @@ +/* +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ + + // Generated from template: + // code_generator/src/json/templates/event_factory.cc.tmp + // and input files: + // <%= template_path %> + +#include "event_factory.h" +#include <unordered_map> +#include "event_type_names.h" +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "core/dom/events/custom_event.h" + +<% _.forEach(data, (item, index) => { %> +<% if (_.isString(item)) { %> +#include "qjs_<%= item %>_event.h" +<% } else if (_.isObject(item)) { %> +#include "qjs_<%= _.snakeCase(item.class) %>.h" +<% } %> +<% }); %> + + +namespace webf { + +using EventConstructorFunction = Event* (*)(ExecutingContext* context, const AtomicString& type, RawEvent* raw_event); + +using EventMap = std::unordered_map<AtomicString, EventConstructorFunction, AtomicString::KeyHasher>; + +static thread_local EventMap* g_event_constructors = nullptr; + +struct CreateEventFunctionMapData { + const AtomicString& tag; + EventConstructorFunction func; +}; + +<% _.forEach(data, (item, index) => { %> + <% if (_.isString(item)) { %> + + static Event* <%= _.upperFirst(item) %>EventConstructor(ExecutingContext* context, const AtomicString& type, RawEvent* raw_event) { + if (raw_event == nullptr) { + return MakeGarbageCollected<<%= _.upperFirst(_.camelCase(item)) %>Event>(context, type, ASSERT_NO_EXCEPTION()); + } + + assert(raw_event->length == sizeof(Native<%= _.upperFirst(item) %>Event) / sizeof(int64_t)); + return MakeGarbageCollected<<%= _.upperFirst(_.camelCase(item)) %>Event>(context, type, toNativeEvent<Native<%= _.upperFirst(item) %>Event>(raw_event)); + } + <% } else if (_.isObject(item)) { %> + static Event* <%= item.class %>Constructor(ExecutingContext* context, const AtomicString& type, RawEvent* raw_event) { + if (raw_event == nullptr) { + return MakeGarbageCollected<<%= item.class %>>(context, type, ASSERT_NO_EXCEPTION()); + } + assert(raw_event->length == sizeof(Native<%= _.upperFirst(item.class) %>) / sizeof(int64_t)); + return MakeGarbageCollected<<%= item.class %>>(context, type, toNativeEvent<Native<%= _.upperFirst(item.class) %>>(raw_event)); + } + <% } %> +<% }); %> + +static void CreateEventFunctionMap() { + assert(!g_event_constructors); + g_event_constructors = new EventMap(); + // 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 CreateEventFunctionMapData data[] = { + + <% _.forEach(data, (item, index) => { %> + <% if (_.isString(item)) { %> + {event_type_names::k<%= item %>, <%= _.upperFirst(item) %>EventConstructor}, + <% } else if (_.isObject(item)) { %> + <% _.forEach(item.types, function(type) { %> + {event_type_names::k<%= type %>, <%= item.class %>Constructor}, + <% }) %> + <% } %> + <% }); %> + + }; + + for (size_t i = 0; i < std::size(data); i++) + g_event_constructors->insert(std::make_pair(data[i].tag, data[i].func)); +} + +Event* EventFactory::Create(ExecutingContext* context, const AtomicString& type, RawEvent* raw_event) { + if (!g_event_constructors) + CreateEventFunctionMap(); + + if (raw_event != nullptr && raw_event->is_custom_event) { + return MakeGarbageCollected<CustomEvent>(context, type, toNativeEvent<NativeCustomEvent>(raw_event)); + } + + auto it = g_event_constructors->find(type); + if (it == g_event_constructors->end()) { + if (raw_event == nullptr) { + return MakeGarbageCollected<Event>(context, type); + } + return MakeGarbageCollected<Event>(context, type, toNativeEvent<NativeEvent>(raw_event)); + } + EventConstructorFunction function = it->second; + return function(context, type, raw_event); +} + +void EventFactory::Dispose() { + delete g_event_constructors; + g_event_constructors = nullptr; +} + +} // namespace webf diff --git a/bridge/scripts/code_generator/templates/json_templates/event_factory.h.tpl b/bridge/scripts/code_generator/templates/json_templates/event_factory.h.tpl new file mode 100644 index 0000000000..b8d3d7bf8c --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/event_factory.h.tpl @@ -0,0 +1,23 @@ +/* +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ + +#ifndef BRIDGE_CORE_EVENT_FACTORY_H_ +#define BRIDGE_CORE_EVENT_FACTORY_H_ + +#include "bindings/qjs/atomic_string.h" +#include "core/dom/events/event.h" + +namespace webf { + +class EventFactory { + public: + // If |local_name| is unknown, nullptr is returned. + static Event* Create(ExecutingContext* context, const AtomicString& type, RawEvent* raw_event); + static void Dispose(); +}; + +} // namespace webf + +#endif // BRIDGE_CORE_EVENT_FACTORY_H_ diff --git a/bridge/scripts/code_generator/templates/json_templates/event_type_helper.h.tpl b/bridge/scripts/code_generator/templates/json_templates/event_type_helper.h.tpl new file mode 100644 index 0000000000..7f82137a64 --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/event_type_helper.h.tpl @@ -0,0 +1,50 @@ + // Generated from template: + // code_generator/src/json/templates/event_type_helper.h.tpl + // and input files: + // <%= template_path %> + +#ifndef BRIDGE_CORE_EVENT_TYPE_HELPER_H_ +#define BRIDGE_CORE_EVENT_TYPE_HELPER_H_ + +#include "core/dom/events/event.h" +#include "event_type_names.h" + +<% _.forEach(data, (item, index) => { %> + <% if (_.isString(item)) { %> +#include "core/events/<%= item %>_event.h" + <% } else if (_.isObject(item)) { %> +#include "core/events/<%= _.snakeCase(item.class) %>.h" + <% } %> +<% }); %> + +namespace webf { + +<% function generateTypeHelperTemplate(name) { + return ` + class ${_.upperFirst(_.camelCase(name))}; + template <> + inline bool IsEventOfType<const ${_.upperFirst(_.camelCase(name))}>(const Event& event) { + return IsA<${_.upperFirst(_.camelCase(name))}>(event); + } + template <> + struct DowncastTraits<${_.upperFirst(_.camelCase(name))}> { + static bool AllowFrom(const Event& event) { + return event.Is${_.upperFirst(_.camelCase(name))}(); + } + }; + `; +} %> + + <% _.forEach(data, (item, index) => { %> + <% if (_.isString(item)) { %> + <%= generateTypeHelperTemplate(item + 'Event') %> + <% } else if (_.isObject(item)) { %> + <%= generateTypeHelperTemplate(item.class) %> + <% } %> + <% }) %> + + +} + + +#endif diff --git a/bridge/scripts/code_generator/templates/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/make_names.cc.tpl new file mode 100644 index 0000000000..1b43b8b6ce --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/make_names.cc.tpl @@ -0,0 +1,87 @@ +// Generated from template: +// code_generator/src/json/templates/make_names.h.tmpl +// and input files: +// <%= template_path %> + +#include "<%= name %>.h" + +namespace webf { +namespace <%= name %> { + +void* names_storage[kNamesCount * ((sizeof(AtomicString) + sizeof(void *) - 1) / sizeof(void *))]; + +<% if (deps && deps.html_attribute_names) { %> +void* html_attribute_names_storage[kHtmlAttributeNamesCount * ((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 %>];<% } %> +<% }) %> + +<% if (deps && deps.html_attribute_names) { %> + <% _.forEach(deps.html_attribute_names.data, function(name, index) { %> + const AtomicString& k<%= upperCamelCase(name) %>Attr = reinterpret_cast<AtomicString*>(&html_attribute_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 %>" }, + <% } %> + <% }); %> + }; + + <% if (deps && deps.html_attribute_names) { %> + static const NameEntry kHtmlAttributeNames[] = { + <% _.forEach(deps.html_attribute_names.data, function(name) { %> + { "<%= 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); + } + + <% if (deps && deps.html_attribute_names) { %> + for(size_t i = 0; i < std::size(kHtmlAttributeNames); i ++) { + void* address = reinterpret_cast<AtomicString*>(&html_attribute_names_storage) + i; + new (address) AtomicString(ctx, kHtmlAttributeNames[i].str); + } + <% } %> +}; + +void Dispose(){ + for(size_t i = 0; i < kNamesCount; i ++) { + AtomicString* atomic_string = reinterpret_cast<AtomicString*>(&names_storage) + i; + atomic_string->~AtomicString(); + } + + <% if (deps && deps.html_attribute_names) { %> + for(size_t i = 0; i < kHtmlAttributeNamesCount; i ++) { + AtomicString* atomic_string = reinterpret_cast<AtomicString*>(&html_attribute_names_storage) + i; + atomic_string->~AtomicString(); + } + <% } %> +}; + + +} +} // webf diff --git a/bridge/scripts/code_generator/templates/json_templates/make_names.h.tpl b/bridge/scripts/code_generator/templates/json_templates/make_names.h.tpl new file mode 100644 index 0000000000..a33e8b3ea9 --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/make_names.h.tpl @@ -0,0 +1,41 @@ +// 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 webf { +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 %>; + <% } %> +<% }) %> + +<% if (deps && deps.html_attribute_names) { %> + constexpr unsigned kHtmlAttributeNamesCount = <%= deps.html_attribute_names.data.length %>; + <% _.forEach(deps.html_attribute_names.data, function(name, index) { %> + extern const AtomicString& k<%= upperCamelCase(name) %>Attr; + <% }) %> +<% } %> + +constexpr unsigned kNamesCount = <%= data.length %>; + +void Init(JSContext* ctx); +void Dispose(); + +} + +} // webf + +#endif // #define <%= _.snakeCase(name).toUpperCase() %> diff --git a/bridge/scripts/code_generator/templates/json_templates/names_installer.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/names_installer.cc.tpl new file mode 100644 index 0000000000..ef88ee482e --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/names_installer.cc.tpl @@ -0,0 +1,25 @@ +// Generated from template: +// code_generator/src/json/templates/names_installer.cc.tmpl + +<% names.forEach(function(k) { %> +#include "<%= k %>.h" +<% }); %> + +namespace webf { +namespace <%= name %> { + +void Init(JSContext* ctx) { +<% names.forEach(function(k) { %> + <%= k %>::Init(ctx); +<% }); %> +} + +void Dispose() { +<% names.forEach(function(k) { %> + <%= k %>::Dispose(); +<% }); %> +} + +} + +} // webf \ No newline at end of file diff --git a/bridge/scripts/code_generator/templates/json_templates/names_installer.h.tpl b/bridge/scripts/code_generator/templates/json_templates/names_installer.h.tpl new file mode 100644 index 0000000000..de63ab7fdd --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/names_installer.h.tpl @@ -0,0 +1,20 @@ +// Generated from template: +// code_generator/src/json/templates/names_installer.h.tmpl + + +#ifndef <%= _.snakeCase(name).toUpperCase() %>_H_ +#define <%= _.snakeCase(name).toUpperCase() %>_H_ + +#include "bindings/qjs/atomic_string.h" + +namespace webf { +namespace <%= name %> { + +void Init(JSContext* ctx); +void Dispose(); + +} + +} // webf + +#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 3d7edee8e2..2fa7e0d8f1 100644 --- a/bridge/test/benchmark/create_element.cc +++ b/bridge/test/benchmark/create_element.cc @@ -4,31 +4,74 @@ */ #include <benchmark/benchmark.h> -#include "page.h" #include "webf_test_env.h" +using namespace webf; + auto bridge = TEST_init(); static void CreateRawJavaScriptObjects(benchmark::State& state) { - auto& context = bridge->getContext(); - std::string code = "var a = {}"; + auto context = bridge->GetExecutingContext(); + uint8_t bytes[] = {1, 2, 2, 97, 12, 97, 97, 97, 46, 106, 115, 14, 0, 6, 0, 160, 1, 0, 1, + 0, 1, 0, 0, 20, 1, 162, 1, 0, 0, 0, 63, 210, 0, 0, 0, 0, 62, 210, + 0, 0, 0, 0, 11, 57, 210, 0, 0, 0, 195, 40, 166, 3, 1, 2, 31, 33}; // Perform setup here for (auto _ : state) { - context->evaluateJavaScript(code.c_str(), code.size(), "internal://", 0); + context->EvaluateByteCode(bytes, sizeof(bytes)); } } 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 = R"( +(() => { +let container = document.createElement('div'); +for(let i = 0; i < 1000; i ++) { + let child = document.createElement('div'); + for(let j = 0; j < 10; j ++) { + let span = document.createElement('span'); + let text = document.createElement('helloworld'); + span.appendChild(text); + child.appendChild(span); + } + container.appendChild(child); +} +})(); +)"; + // Perform setup here + for (auto _ : state) { + context->EvaluateJavaScript(code.c_str(), code.size(), "internal://", 0); + } +} + +static void InsertElement(benchmark::State& state) { + auto context = bridge->GetExecutingContext(); + std::string code = R"( +(() => { +let container = document.createElement('div'); +let child = document.createElement('div'); +let span = document.createElement('span'); +let text = document.createElement('helloworld'); +span.appendChild(text); +container.appendChild(child); + +for(let i = 0; i < 1000; i ++) { + let span = document.createElement('span'); + let text = document.createElement('helloworld'); + span.appendChild(text); + child.insertBefore(span, child.firstChild); +} +})(); +)"; // Perform setup here for (auto _ : state) { - context->evaluateJavaScript(code.c_str(), code.size(), "internal://", 0); + context->EvaluateJavaScript(code.c_str(), code.size(), "internal://", 0); } } BENCHMARK(CreateRawJavaScriptObjects)->Threads(1); BENCHMARK(CreateDivElement)->Threads(1); +BENCHMARK(InsertElement)->Threads(1); // Run the benchmark BENCHMARK_MAIN(); diff --git a/bridge/test/run_integration_test.cc b/bridge/test/run_integration_test.cc index 87db3a4cad..19226ddc08 100644 --- a/bridge/test/run_integration_test.cc +++ b/bridge/test/run_integration_test.cc @@ -4,8 +4,12 @@ */ #include <fstream> +#include "foundation/logging.h" #include "gtest/gtest.h" -#include "page.h" +#include "webf_bridge_test.h" +#include "webf_test_env.h" + +using namespace webf; #include "webf_bridge_test.h" #include "webf_test_env.h" @@ -31,12 +35,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* { WEBF_LOG(VERBOSE) << "done"; }); + executeTest(context->contextId(), [](int32_t contextId, void* status) -> void* { + WEBF_LOG(VERBOSE) << "done"; + return nullptr; + }); TEST_runLoop(context); } diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index 2e7dc68550..972acc4c39 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -10,28 +10,31 @@ add_subdirectory(./third_party/googletest) add_subdirectory(./third_party/benchmark) list(APPEND WEBF_TEST_SOURCE - page_test.cc - page_test.h -) + test/webf_test_context.cc + test/webf_test_context.h + ) list(APPEND WEBF_UNIT_TEST_SOURCEURCE ./test/webf_test_env.cc ./test/webf_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 + ./bindings/qjs/qjs_engine_patch_test.cc + ./core/dom/events/custom_event_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/legacy/element_attribute_test.cc + ./core/dom/node_test.cc + ./core/html/legacy/html_collection_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 + ./core/html/html_element_test.cc + ./core/html/custom/widget_element_test.cc + ./core/timing/performance_test.cc ) ### webf_unit_test executable @@ -95,6 +98,7 @@ add_library(webf_test SHARED ${WEBF_TEST_SOURCE}) target_link_libraries(webf_test PRIVATE ${BRIDGE_LINK_LIBS} webf) target_include_directories(webf_test PRIVATE ${BRIDGE_INCLUDE} + ./test ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ./include) if (DEFINED ENV{LIBRARY_OUTPUT_DIR}) diff --git a/bridge/test/webf_test_context.cc b/bridge/test/webf_test_context.cc new file mode 100644 index 0000000000..f5fb478a6a --- /dev/null +++ b/bridge/test/webf_test_context.cc @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "webf_test_context.h" +#include "bindings/qjs/member_installer.h" +#include "core/dom/document.h" +#include "core/fileapi/blob.h" +#include "core/html/html_body_element.h" +#include "core/html/html_html_element.h" +#include "core/html/parser/html_parser.h" +#include "qjs_blob.h" +#include "testframework.h" + +namespace webf { + +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<WebFPage*>(context->owner()); + auto bridgeTest = static_cast<WebFTestContext*>(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 '__webf_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 '__webf_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); + } + + if (!JS_IsString(screenShotValue)) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__webf_match_image_snapshot__': parameter 2 (match) must be an string."); + } + + if (!JS_IsObject(callbackValue)) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__webf_match_image_snapshot__': parameter 3 (callback) is not an function."); + } + + if (!JS_IsFunction(ctx, callbackValue)) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__webf_match_image_snapshot__': parameter 3 (callback) is not an function."); + } + + if (context->dartMethodPtr()->matchImageSnapshot == nullptr) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__webf_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); + } + + std::unique_ptr<NativeString> screenShotNativeString = webf::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 '__webf_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 '__webf_simulate_pointer__': dart method(simulatePointer) is not registered."); + } + + JSValue inputArrayValue = argv[0]; + if (!JS_IsObject(inputArrayValue)) { + return JS_ThrowTypeError(ctx, "Failed to execute '__webf_simulate_pointer__': first arguments should be an array."); + } + + JSValue pointerValue = argv[1]; + if (!JS_IsNumber(pointerValue)) { + return JS_ThrowTypeError(ctx, + "Failed to execute '__webf_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++) { + MousePointer* mouse = &mousePointerList[i]; + 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; + + 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 '__webf_simulate_keypress__': dart method(simulateInputText) is not registered."); + } + + JSValue& charStringValue = argv[0]; + + if (!JS_IsString(charStringValue)) { + return JS_ThrowTypeError(ctx, "Failed to execute '__webf_simulate_keypress__': first arguments should be a string"); + } + + std::unique_ptr<NativeString> nativeString = webf::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)); + MemberMutationScope scope(context); + + if (argc == 1) { + std::string strHTML = AtomicString(ctx, argv[0]).ToStdString(); + HTMLParser::parseHTML(strHTML, context->document()->documentElement()); + } + + 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 WebFTestContext::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"); + } + + WEBF_LOG(VERBOSE) << "Done.."; + + std::unique_ptr<NativeString> status = webf::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; +} + +WebFTestContext::WebFTestContext(ExecutingContext* context) + : context_(context), page_(static_cast<WebFPage*>(context->owner())) { + page_->owner = this; + page_->disposeCallback = [](WebFPage* bridge) { delete static_cast<WebFTestContext*>(bridge->owner); }; + + std::initializer_list<MemberInstaller::FunctionConfig> functionConfig{ + {"__webf_execute_test__", executeTest, 1}, + {"__webf_match_image_snapshot__", matchImageSnapshot, 3}, + {"__webf_environment__", environment, 0}, + {"__webf_simulate_pointer__", simulatePointer, 1}, + {"__webf_simulate_inputtext__", simulateInputText, 1}, + {"__webf_trigger_global_error__", triggerGlobalError, 0}, + {"__webf_parse_html__", parseHTML, 1}, + }; + + MemberInstaller::InstallFunctions(context, context->Global(), functionConfig); + initWebFTestFramework(context); +} + +bool WebFTestContext::evaluateTestScripts(const uint16_t* code, + size_t codeLength, + const char* sourceURL, + int startLine) { + if (!context_->IsContextValid()) + return false; + return context_->EvaluateJavaScript(code, codeLength, sourceURL, startLine); +} + +bool WebFTestContext::parseTestHTML(const uint16_t* code, size_t codeLength) { + if (!context_->IsContextValid()) + return false; + std::string utf8Code = toUTF8(std::u16string(reinterpret_cast<const char16_t*>(code), codeLength)); + return page_->parseHTML(utf8Code.c_str(), utf8Code.length()); +} + +void WebFTestContext::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 webf diff --git a/bridge/test/webf_test_context.h b/bridge/test/webf_test_context.h new file mode 100644 index 0000000000..2cece1ee7f --- /dev/null +++ b/bridge/test/webf_test_context.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#ifndef BRIDGE_WEBF_TEST_CONTEXT_H +#define BRIDGE_WEBF_TEST_CONTEXT_H + +#include "bindings/qjs/qjs_function.h" +#include "core/executing_context.h" +#include "core/page.h" +#include "webf_bridge_test.h" + +namespace webf { + +struct ImageSnapShotContext { + JSValue callback; + ExecutingContext* context; + list_head link; +}; + +class WebFTestContext final { + public: + explicit WebFTestContext() = delete; + explicit WebFTestContext(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}; + WebFPage* page_; +}; + +} // namespace webf + +#endif // BRIDGE_WEBF_TEST_CONTEXT_H diff --git a/bridge/test/webf_test_env.cc b/bridge/test/webf_test_env.cc index d5098ceaba..987c82d668 100644 --- a/bridge/test/webf_test_env.cc +++ b/bridge/test/webf_test_env.cc @@ -3,14 +3,17 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#include "webf_test_env.h" #include <sys/time.h> #include <vector> -#include "bindings/qjs/dom/event_target.h" -#include "dart_methods.h" -#include "include/webf_bridge.h" -#include "page.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 "foundation/native_value_converter.h" #include "webf_bridge_test.h" +#include "webf_test_env.h" #if defined(__linux__) || defined(__APPLE__) static int64_t get_time_ms(void) { @@ -27,10 +30,12 @@ static int64_t get_time_ms(void) { } #endif +namespace webf { + typedef struct { struct list_head link; int64_t timeout; - DOMTimer* timer; + webf::DOMTimer* timer; int32_t contextId; bool isInterval; AsyncCallback func; @@ -38,7 +43,7 @@ typedef struct { typedef struct { struct list_head link; - FrameCallback* callback; + webf::FrameCallback* callback; int32_t contextId; AsyncRAFCallback handler; int32_t callbackId; @@ -49,22 +54,35 @@ 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) { +NativeValue* 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") { + NativeValue data = Native_NewCString("{\"result\": 1234}"); + callback(callbackContext, contextId, nullptr, &data); + } + + auto* result = static_cast<NativeValue*>(malloc(sizeof(NativeValue))); + NativeValue tmp = Native_NewCString(module); + memcpy(result, &tmp, sizeof(NativeValue)); + return result; }; void TEST_requestBatchUpdate(int32_t contextId){}; @@ -73,9 +91,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(webf::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; @@ -90,9 +108,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(webf::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; @@ -109,14 +127,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(webf::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; @@ -128,15 +146,15 @@ uint32_t TEST_requestAnimationFrame(FrameCallback* frameCallback, int32_t contex void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { auto* page = static_cast<webf::WebFPage*>(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<webf::WebFPage*>(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); } @@ -152,13 +170,18 @@ 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_flushUICommand() {} - -void TEST_initWindow(int32_t contextId, void* nativePtr) {} +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_initDocument(int32_t contextId, void* nativePtr) {} +void TEST_flushUICommand(int32_t contextId) { + clearUICommandItems(contextId); +} void TEST_onJsLog(int32_t contextId, int32_t level, const char*) {} @@ -173,22 +196,23 @@ static int32_t inited{false}; std::unique_ptr<webf::WebFPage> TEST_init(OnJSError onJsError) { uint32_t contextId; + auto mockedDartMethods = TEST_getMockDartMethods(onJsError); if (inited) { - contextId = allocateNewPage(-1); + contextId = allocateNewPage(-1, mockedDartMethods.data(), mockedDartMethods.size()); } else { contextId = 0; } - std::call_once(testInitOnceFlag, []() { - initJSPagePool(1024 * 1024); + std::call_once(testInitOnceFlag, [&mockedDartMethods]() { + initJSPagePool(1024 * 1024, mockedDartMethods.data(), mockedDartMethods.size()); inited = true; }); + initTestFramework(contextId); + TEST_mockTestEnvDartMethods(contextId, onJsError); auto* page = static_cast<webf::WebFPage*>(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<webf::WebFPage>(page); } @@ -197,14 +221,16 @@ std::unique_ptr<webf::WebFPage> TEST_init() { return TEST_init(nullptr); } -std::unique_ptr<webf::WebFPage> TEST_allocateNewPage() { - uint32_t newContextId = allocateNewPage(-1); +std::unique_ptr<webf::WebFPage> TEST_allocateNewPage(OnJSError onJsError) { + auto mockedDartMethods = TEST_getMockDartMethods(onJsError); + uint32_t newContextId = allocateNewPage(-1, mockedDartMethods.data(), mockedDartMethods.size()); + initTestFramework(newContextId); return std::unique_ptr<webf::WebFPage>(static_cast<webf::WebFPage*>(getPage(newContextId))); } -static bool jsPool(ExecutionContext* context) { - JSRuntime* rt = context->runtime(); +static bool jsPool(webf::ExecutingContext* context) { + JSRuntime* rt = ScriptState::runtime(); JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(rt)); int64_t cur_time, delay; struct list_head* el; @@ -226,8 +252,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; @@ -249,50 +276,33 @@ static bool jsPool(ExecutionContext* context) { return false; } -void TEST_runLoop(ExecutionContext* context) { +void TEST_runLoop(webf::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_onJSLog(int32_t contextId, int32_t level, const char*) {} +void TEST_onMatchImageSnapshot(void* callbackContext, + int32_t contextId, + uint8_t* bytes, + int32_t length, + NativeString* name, + MatchImageSnapshotCallback callback) { + callback(callbackContext, contextId, 1, nullptr); } -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]; +const char* TEST_environment() { + return ""; } -void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback) { - if (unitTestEnvMap.count(contextUniqueId) == 0) { - unitTestEnvMap[contextUniqueId] = std::make_shared<UnitTestEnv>(); - } +void TEST_simulatePointer(MousePointer*, int32_t length, int32_t pointer) {} - unitTestEnvMap[contextUniqueId]->onEventTargetDisposed = callback; -} +void TEST_simulateInputText(NativeString* nativeString) {} -void TEST_mockDartMethods(OnJSError onJSError) { +std::vector<uint64_t> TEST_getMockDartMethods(OnJSError onJSError) { std::vector<uint64_t> mockMethods{ reinterpret_cast<uint64_t>(TEST_invokeModule), reinterpret_cast<uint64_t>(TEST_requestBatchUpdate), @@ -302,11 +312,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 @@ -317,5 +324,36 @@ 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()); + return mockMethods; } + +void TEST_mockTestEnvDartMethods(int32_t contextId, OnJSError onJSError) { + std::vector<uint64_t> mockMethods{ + reinterpret_cast<uint64_t>(onJSError), + reinterpret_cast<uint64_t>(TEST_onMatchImageSnapshot), + reinterpret_cast<uint64_t>(TEST_environment), + reinterpret_cast<uint64_t>(TEST_simulatePointer), + reinterpret_cast<uint64_t>(TEST_simulateInputText), + }; + + registerTestEnvDartMethods(contextId, mockMethods.data(), mockMethods.size()); +} + +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 context_unique_id, TEST_OnEventTargetDisposed callback) { + if (unitTestEnvMap.count(context_unique_id) == 0) { + unitTestEnvMap[context_unique_id] = std::make_shared<UnitTestEnv>(); + } + + unitTestEnvMap[context_unique_id]->on_event_target_disposed = callback; +} + +} // namespace webf diff --git a/bridge/test/webf_test_env.h b/bridge/test/webf_test_env.h index 2a71828d96..96262e5ecf 100644 --- a/bridge/test/webf_test_env.h +++ b/bridge/test/webf_test_env.h @@ -7,31 +7,34 @@ #define BRIDGE_TEST_WEBF_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 "bindings/qjs/cppgc/mutation_scope.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" -using namespace webf::binding::qjs; +using namespace webf; // Trigger a callbacks before GC free the eventTargets. -using TEST_OnEventTargetDisposed = void (*)(webf::binding::qjs::EventTargetInstance* eventTargetInstance); +using TEST_OnEventTargetDisposed = void (*)(EventTarget* event_target); struct UnitTestEnv { - TEST_OnEventTargetDisposed onEventTargetDisposed{nullptr}; + TEST_OnEventTargetDisposed on_event_target_disposed{nullptr}; }; // Mock dart methods and add async timer to emulate webf environment in C++ unit test. -std::unique_ptr<webf::WebFPage> TEST_init(OnJSError onJsError); -std::unique_ptr<webf::WebFPage> TEST_init(); -std::unique_ptr<webf::WebFPage> 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 webf { + +std::unique_ptr<WebFPage> TEST_init(OnJSError onJsError); +std::unique_ptr<WebFPage> TEST_init(); +std::unique_ptr<WebFPage> TEST_allocateNewPage(OnJSError onJsError); +void TEST_runLoop(ExecutingContext* context); +std::vector<uint64_t> TEST_getMockDartMethods(OnJSError onJSError); +void TEST_mockTestEnvDartMethods(int32_t contextId, OnJSError onJSError); +void TEST_registerEventTargetDisposedCallback(int32_t context_unique_id, TEST_OnEventTargetDisposed callback); +std::shared_ptr<UnitTestEnv> TEST_getEnv(int32_t context_unique_id); +} // namespace webf + // 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); #endif // BRIDGE_TEST_WEBF_TEST_ENV_H_ diff --git a/bridge/third_party/quickjs/include/quickjs/quickjs.h b/bridge/third_party/quickjs/include/quickjs/quickjs.h index 0a5a0c3022..89fdb23ad4 100644 --- a/bridge/third_party/quickjs/include/quickjs/quickjs.h +++ b/bridge/third_party/quickjs/include/quickjs/quickjs.h @@ -188,6 +188,12 @@ static inline JS_BOOL JS_VALUE_IS_NAN(JSValue v) { #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; @@ -408,6 +414,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/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/quickjs-atom.h" +#undef DEF + ; JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len); JSAtom JS_NewAtom(JSContext *ctx, const char *str); 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/src/core/base.h b/bridge/third_party/quickjs/src/core/base.h index f02747fc81..03a9ebd10f 100644 --- a/bridge/third_party/quickjs/src/core/base.h +++ b/bridge/third_party/quickjs/src/core/base.h @@ -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 diff --git a/bridge/third_party/quickjs/src/core/string.h b/bridge/third_party/quickjs/src/core/string.h index b09fdc4a1a..b23e1c93aa 100644 --- a/bridge/third_party/quickjs/src/core/string.h +++ b/bridge/third_party/quickjs/src/core/string.h @@ -32,10 +32,6 @@ #define ATOM_GET_STR_BUF_SIZE 64 -#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) diff --git a/bridge/third_party/quickjs/src/core/types.h b/bridge/third_party/quickjs/src/core/types.h index a9316e6595..728631f0f6 100644 --- a/bridge/third_party/quickjs/src/core/types.h +++ b/bridge/third_party/quickjs/src/core/types.h @@ -876,22 +876,6 @@ struct JSObject { /* byte sizes: 40/48/72 */ }; -enum { - __JS_ATOM_NULL = JS_ATOM_NULL, -#define DEF(name, str) JS_ATOM_ ## name, -#include "quickjs/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/quickjs-atom.h" -#undef DEF - ; - typedef enum OPCodeFormat { #define FMT(f) OP_FMT_ ## f, #define DEF(id, size, n_pop, n_push, f) 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/bridge/webf_bridge.cc b/bridge/webf_bridge.cc index 2711704c8f..1736aeae5c 100644 --- a/bridge/webf_bridge.cc +++ b/bridge/webf_bridge.cc @@ -1,18 +1,19 @@ /* - * Copyright (C) 2019-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#include "webf_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 WEBF_QUICK_JS_ENGINE -#include "page.h" -#endif - -#include <atomic> -#include <thread> +#include "include/webf_bridge.h" #if defined(_WIN32) #define SYSTEM_NAME "windows" // Windows @@ -39,26 +40,9 @@ // this is not thread safe std::atomic<bool> inited{false}; +std::atomic<bool> is_dart_hot_restart{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 (webf::getDartMethod()->onJsError != nullptr) { - webf::getDartMethod()->onJsError(contextId, errmsg); - } - if (webf::getDartMethod()->onJsLog != nullptr) { - webf::getDartMethod()->onJsLog(contextId, static_cast<int>(foundation::MessageLevel::Error), errmsg); - } - - WEBF_LOG(ERROR) << errmsg << std::endl; -} namespace { @@ -81,18 +65,25 @@ int32_t searchForAvailableContextId() { } // namespace -void initJSPagePool(int poolSize) { - uiThreadId = std::this_thread::get_id(); +namespace webf { +bool isDartHotRestart() { + return is_dart_hot_restart; +} +} // namespace webf + +void initJSPagePool(int poolSize, uint64_t* dart_methods, int32_t dart_methods_len) { // When dart hot restarted, should dispose previous bridge and clear task message queue. if (inited) { + is_dart_hot_restart = true; disposeAllPages(); + is_dart_hot_restart = false; }; webf::WebFPage::pageContextPool = new webf::WebFPage*[poolSize]; for (int i = 1; i < poolSize; i++) { webf::WebFPage::pageContextPool[i] = nullptr; } - webf::WebFPage::pageContextPool[0] = new webf::WebFPage(0, printError); + webf::WebFPage::pageContextPool[0] = new webf::WebFPage(0, nullptr, dart_methods, dart_methods_len); inited = true; maxPoolSize = poolSize; } @@ -107,7 +98,7 @@ void disposePage(int32_t contextId) { webf::WebFPage::pageContextPool[contextId] = nullptr; } -int32_t allocateNewPage(int32_t targetContextId) { +int32_t allocateNewPage(int32_t targetContextId, uint64_t* dart_methods, int32_t dart_methods_len) { if (targetContextId == -1) { targetContextId = ++poolIndex; } @@ -117,8 +108,10 @@ int32_t allocateNewPage(int32_t targetContextId) { } assert(webf::WebFPage::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 webf::WebFPage(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 webf::WebFPage(targetContextId, nullptr, dart_methods, dart_methods_len); webf::WebFPage::pageContextPool[targetContextId] = page; return targetContextId; } @@ -137,13 +130,13 @@ bool checkPage(int32_t contextId, void* context) { if (webf::WebFPage::pageContextPool[contextId] == nullptr) return false; auto* page = static_cast<webf::WebFPage*>(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<webf::WebFPage*>(getPage(contextId)); - context->evaluateScript(code, bundleFilename, startLine); + context->evaluateScript(reinterpret_cast<webf::NativeString*>(code), bundleFilename, startLine); } void evaluateQuickjsByteCode(int32_t contextId, uint8_t* bytes, int32_t byteLen) { @@ -158,29 +151,25 @@ void parseHTML(int32_t contextId, const char* code, int32_t length) { context->parseHTML(code, length); } -void reloadJsContext(int32_t contextId) { +void reloadJsContext(int32_t contextId, uint64_t* dart_methods, int32_t dart_methods_len) { assert(checkPage(contextId) && "reloadJSContext: contextId is not valid"); auto bridgePtr = getPage(contextId); auto context = static_cast<webf::WebFPage*>(bridgePtr); - auto newContext = new webf::WebFPage(contextId, printError); + auto newContext = new webf::WebFPage(contextId, nullptr, dart_methods, dart_methods_len); delete context; webf::WebFPage::pageContextPool[contextId] = newContext; } -void invokeModuleEvent(int32_t contextId, NativeString* moduleName, const char* eventType, void* event, NativeString* extra) { +NativeValue* invokeModuleEvent(int32_t contextId, + NativeString* moduleName, + const char* eventType, + void* event, + NativeValue* extra) { assert(checkPage(contextId) && "invokeEventListener: contextId is not valid"); auto context = static_cast<webf::WebFPage*>(getPage(contextId)); - context->invokeModuleEvent(moduleName, eventType, event, extra); -} - -void registerDartMethods(uint64_t* methodBytes, int32_t length) { - webf::registerDartMethods(methodBytes, length); -} - -NativeScreen* createScreen(double width, double height) { - screen.width = width; - screen.height = height; - return &screen; + auto* result = context->invokeModuleEvent(reinterpret_cast<webf::NativeString*>(moduleName), eventType, event, + reinterpret_cast<webf::NativeValue*>(extra)); + return reinterpret_cast<NativeValue*>(result); } static WebFInfo* webfInfo{nullptr}; @@ -202,41 +191,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<webf::WebFPage*>(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(); + webf::UITaskQueue::instance(contextId)->flushTask(); } void registerUITask(int32_t contextId, Task task, void* data) { - foundation::UITaskQueue::instance(contextId)->registerTask(task, data); + webf::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<webf::WebFPage*>(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<webf::WebFPage*>(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<webf::WebFPage*>(getPage(contextId)); if (page == nullptr) return; - page->getContext()->uiCommandBuffer()->clear(); + page->GetExecutingContext()->uiCommandBuffer()->clear(); } void registerContextDisposedCallbacks(int32_t contextId, Task task, void* data) { @@ -245,7 +231,7 @@ void registerContextDisposedCallbacks(int32_t contextId, Task task, void* data) } void registerPluginByteCode(uint8_t* bytes, int32_t length, const char* pluginName) { - webf::WebFPage::pluginByteCode[pluginName] = NativeByteCode{bytes, length}; + webf::ExecutingContext::pluginByteCode[pluginName] = webf::NativeByteCode{bytes, length}; } int32_t profileModeEnabled() { @@ -255,17 +241,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/webf_bridge_test.cc b/bridge/webf_bridge_test.cc index a6254dadda..f98102972a 100644 --- a/bridge/webf_bridge_test.cc +++ b/bridge/webf_bridge_test.cc @@ -1,35 +1,33 @@ /* - * Copyright (C) 2020-present The Kraken authors. All rights reserved. + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "webf_bridge_test.h" -#include "dart_methods.h" - -#if WEBF_JSC_ENGINE -#include "bridge_test_jsc.h" -#elif WEBF_QUICK_JS_ENGINE -#include "page_test.h" -#endif #include <atomic> +#include "bindings/qjs/native_string_utils.h" +#include "webf_test_context.h" -std::unordered_map<int, webf::WebFPageTest*> bridgeTestPool = std::unordered_map<int, webf::WebFPageTest*>(); +std::unordered_map<int, webf::WebFTestContext*> testContextPool = std::unordered_map<int, webf::WebFTestContext*>(); void initTestFramework(int32_t contextId) { auto* page = static_cast<webf::WebFPage*>(getPage(contextId)); - auto bridgeTest = new webf::WebFPageTest(page); - bridgeTestPool[contextId] = bridgeTest; + auto testContext = new webf::WebFTestContext(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<webf::NativeString*>(code)->string(), + static_cast<webf::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) { - webf::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/integration_tests/lib/bridge/from_native.dart b/integration_tests/lib/bridge/from_native.dart index 27bde2f385..27bb2f7aa2 100644 --- a/integration_tests/lib/bridge/from_native.dart +++ b/integration_tests/lib/bridge/from_native.dart @@ -46,17 +46,28 @@ void _onJSError(int contextId, Pointer<Utf8> charStr) { _listenerList[contextId](msg); } -final Pointer<NativeFunction<NativeJSError>> _nativeOnJsError = Pointer.fromFunction(_onJSError); +final Pointer<NativeFunction<NativeJSError>> _nativeOnJsError = + Pointer.fromFunction(_onJSError); typedef NativeMatchImageSnapshotCallback = Void Function( Pointer<Void> callbackContext, Int32 contextId, Int8, Pointer<Utf8>); typedef DartMatchImageSnapshotCallback = void Function( Pointer<Void> callbackContext, int contextId, int, Pointer<Utf8>); -typedef NativeMatchImageSnapshot = Void Function(Pointer<Void> callbackContext, Int32 contextId, Pointer<Uint8>, Int32, - Pointer<NativeString>, Pointer<NativeFunction<NativeMatchImageSnapshotCallback>>); - -void _matchImageSnapshot(Pointer<Void> callbackContext, int contextId, Pointer<Uint8> bytes, int size, - Pointer<NativeString> snapshotNamePtr, Pointer<NativeFunction<NativeMatchImageSnapshotCallback>> pointer) { +typedef NativeMatchImageSnapshot = Void Function( + Pointer<Void> callbackContext, + Int32 contextId, + Pointer<Uint8>, + Int32, + Pointer<NativeString>, + Pointer<NativeFunction<NativeMatchImageSnapshotCallback>>); + +void _matchImageSnapshot( + Pointer<Void> callbackContext, + int contextId, + Pointer<Uint8> bytes, + int size, + Pointer<NativeString> snapshotNamePtr, + Pointer<NativeFunction<NativeMatchImageSnapshotCallback>> pointer) { DartMatchImageSnapshotCallback callback = pointer.asFunction(); String filename = nativeStringToString(snapshotNamePtr); matchImageSnapshot(bytes.asTypedList(size), filename).then((value) { @@ -67,8 +78,8 @@ void _matchImageSnapshot(Pointer<Void> callbackContext, int contextId, Pointer<U }); } -final Pointer<NativeFunction<NativeMatchImageSnapshot>> _nativeMatchImageSnapshot = - Pointer.fromFunction(_matchImageSnapshot); +final Pointer<NativeFunction<NativeMatchImageSnapshot>> + _nativeMatchImageSnapshot = Pointer.fromFunction(_matchImageSnapshot); typedef NativeEnvironment = Pointer<Utf8> Function(); typedef DartEnvironment = Pointer<Utf8> Function(); @@ -77,9 +88,11 @@ Pointer<Utf8> _environment() { return (jsonEncode(Platform.environment)).toNativeUtf8(); } -final Pointer<NativeFunction<NativeEnvironment>> _nativeEnvironment = Pointer.fromFunction(_environment); +final Pointer<NativeFunction<NativeEnvironment>> _nativeEnvironment = + Pointer.fromFunction(_environment); -typedef NativeSimulatePointer = Void Function(Pointer<Pointer<MousePointer>>, Int32 length, Int32 pointer); +typedef NativeSimulatePointer = Void Function( + Pointer<MousePointer>, Int32 length, Int32 pointer); typedef NativeSimulateInputText = Void Function(Pointer<NativeString>); PointerChange _getPointerChange(double change) { @@ -100,15 +113,15 @@ class MousePointer extends Struct { external double change; } -void _simulatePointer(Pointer<Pointer<MousePointer>> mousePointerList, int length, int pointer) { +void _simulatePointer( + Pointer<MousePointer> mousePointerList, int length, int pointer) { List<PointerData> data = []; for (int i = 0; i < length; i++) { - int contextId = mousePointerList[i].ref.contextId; - double x = mousePointerList[i].ref.x; - double y = mousePointerList[i].ref.y; - - double change = mousePointerList[i].ref.change; + int contextId = mousePointerList.elementAt(i).ref.contextId; + double x = mousePointerList.elementAt(i).ref.x; + double y = mousePointerList.elementAt(i).ref.y; + double change = mousePointerList.elementAt(i).ref.change; data.add(PointerData( // TODO: remove hardcode '360' width that for double testing in one flutter window physicalX: (360 * contextId + x) * window.devicePixelRatio, @@ -125,7 +138,8 @@ void _simulatePointer(Pointer<Pointer<MousePointer>> mousePointerList, int lengt window.onPointerDataPacket!(dataPacket); } -final Pointer<NativeFunction<NativeSimulatePointer>> _nativeSimulatePointer = Pointer.fromFunction(_simulatePointer); +final Pointer<NativeFunction<NativeSimulatePointer>> _nativeSimulatePointer = + Pointer.fromFunction(_simulatePointer); late TestTextInput testTextInput; void _simulateInputText(Pointer<NativeString> nativeChars) { @@ -133,8 +147,8 @@ void _simulateInputText(Pointer<NativeString> nativeChars) { testTextInput.enterText(text); } -final Pointer<NativeFunction<NativeSimulateInputText>> _nativeSimulateInputText = - Pointer.fromFunction(_simulateInputText); +final Pointer<NativeFunction<NativeSimulateInputText>> + _nativeSimulateInputText = Pointer.fromFunction(_simulateInputText); final List<int> _dartNativeMethods = [ _nativeOnJsError.address, @@ -144,16 +158,21 @@ final List<int> _dartNativeMethods = [ _nativeSimulateInputText.address ]; -typedef NativeRegisterTestEnvDartMethods = Void Function(Pointer<Uint64> methodBytes, Int32 length); -typedef DartRegisterTestEnvDartMethods = 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 DartRegisterTestEnvDartMethods _registerTestEnvDartMethods = WebFDynamicLibrary.ref - .lookup<NativeFunction<NativeRegisterTestEnvDartMethods>>('registerTestEnvDartMethods') - .asFunction(); +final Dart_RegisterTestEnvDartMethods _registerTestEnvDartMethods = + WebFDynamicLibrary.ref + .lookup<NativeFunction<Native_RegisterTestEnvDartMethods>>( + 'registerTestEnvDartMethods') + .asFunction(); -void registerDartTestMethodsToCpp() { - Pointer<Uint64> bytes = malloc.allocate<Uint64>(sizeOf<Uint64>() * _dartNativeMethods.length); +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/custom/custom_element.dart b/integration_tests/lib/custom/custom_element.dart index e620da9e56..247e3e7d1e 100644 --- a/integration_tests/lib/custom/custom_element.dart +++ b/integration_tests/lib/custom/custom_element.dart @@ -19,7 +19,8 @@ class WaterfallFlowWidgetElement extends WidgetElement { } @override - Widget build(BuildContext context, Map<String, dynamic> properties, List<Widget> children) { + Widget build(BuildContext context, Map<String, dynamic> properties, + List<Widget> children) { _children = children; return WaterfallFlow.builder( @@ -29,8 +30,9 @@ class WaterfallFlowWidgetElement extends WidgetElement { crossAxisCount: 2, crossAxisSpacing: 5.0, mainAxisSpacing: 5.0, - lastChildLayoutTypeBuilder: (index) => - index == children.length ? LastChildLayoutType.foot : LastChildLayoutType.none, + lastChildLayoutTypeBuilder: (index) => index == children.length + ? LastChildLayoutType.foot + : LastChildLayoutType.none, ), ); } @@ -40,9 +42,11 @@ class TextWidgetElement extends WidgetElement { TextWidgetElement(BindingContext? context) : super(context); @override - Widget build(BuildContext context, Map<String, dynamic> properties, List<Widget> children) { + Widget build(BuildContext context, Map<String, dynamic> properties, + List<Widget> children) { return Text(properties['value'] ?? '', - textDirection: TextDirection.ltr, style: TextStyle(color: Color.fromARGB(255, 100, 100, 100))); + textDirection: TextDirection.ltr, + style: TextStyle(color: Color.fromARGB(255, 100, 100, 100))); } } @@ -50,7 +54,8 @@ class ImageWidgetElement extends WidgetElement { ImageWidgetElement(BindingContext? context) : super(context); @override - Widget build(BuildContext context, Map<String, dynamic> properties, List<Widget> children) { + Widget build(BuildContext context, Map<String, dynamic> properties, + List<Widget> children) { return Image(image: AssetImage(properties['src'])); } } @@ -59,7 +64,8 @@ class ContainerWidgetElement extends WidgetElement { ContainerWidgetElement(BindingContext? context) : super(context); @override - Widget build(BuildContext context, Map<String, dynamic> properties, List<Widget> children) { + Widget build(BuildContext context, Map<String, dynamic> properties, + List<Widget> children) { return Container( width: 200, height: 200, @@ -92,9 +98,26 @@ class SampleElement extends dom.Element implements BindingObject { return asyncFn; case 'asyncFnFailed': return asyncFnFailed; + case 'asyncFnNotComplete': + return asyncFnNotComplete; + default: + return super.getBindingProperty(key); } } + @override + void getAllBindingPropertyNames(List<String> properties) { + super.getAllBindingPropertyNames(properties); + properties.addAll([ + 'ping', + 'fake', + 'fn', + 'asyncFn', + 'asyncFnFailed', + 'asyncFnNotComplete' + ]); + } + String get ping => 'pong'; int get fake => 1234; @@ -107,12 +130,17 @@ class SampleElement extends dom.Element implements BindingObject { Function get asyncFn => (List<dynamic> argv) async { Completer<dynamic> completer = Completer(); - Timer(Duration(seconds: 1), () { + Timer(Duration(milliseconds: 200), () { completer.complete(argv[0]); }); return completer.future; }; + Function get asyncFnNotComplete => (List<dynamic> argv) async { + Completer<dynamic> completer = Completer(); + return completer.future; + }; + Function get asyncFnFailed => (List<dynamic> args) async { Completer<String> completer = Completer(); Timer(Duration(milliseconds: 100), () { diff --git a/integration_tests/lib/main.dart b/integration_tests/lib/main.dart index e80b44e795..843527cdbc 100644 --- a/integration_tests/lib/main.dart +++ b/integration_tests/lib/main.dart @@ -12,6 +12,7 @@ import 'package:webf/dom.dart'; import 'package:webf/gesture.dart'; import 'package:webf/webf.dart'; +import 'test_module.dart'; import 'bridge/from_native.dart'; import 'bridge/test_input.dart'; import 'bridge/to_native.dart'; @@ -22,7 +23,7 @@ String? pass = (AnsiPen()..green())('[TEST PASS]'); String? err = (AnsiPen()..red())('[TEST FAILED]'); final String __dirname = path.dirname(Platform.script.path); -final String testDirectory = Platform.environment['KRAKEN_TEST_DIR'] ?? __dirname; +final String testDirectory = Platform.environment['WEBF_TEST_DIR'] ?? __dirname; // By CLI: `KRAKEN_ENABLE_TEST=true flutter run` void main() async { @@ -30,6 +31,8 @@ void main() async { WebFDynamicLibrary.libName = 'libwebf_test'; defineWebFCustomElements(); + ModuleManager.defineModule((moduleManager) => DemoModule(moduleManager)); + // FIXME: This is a workaround for testcases. ParagraphElement.defaultStyle = {DISPLAY: BLOCK}; @@ -49,8 +52,8 @@ void main() async { final File spec = File(path.join(testDirectory, specTarget)); WebFJavaScriptChannel javaScriptChannel = WebFJavaScriptChannel(); javaScriptChannel.onMethodCall = (String method, dynamic arguments) async { - javaScriptChannel.invokeMethod(method, arguments); - return 'method: ' + method; + dynamic returnedValue = await javaScriptChannel.invokeMethod(method, arguments); + return 'method: $method, return_type: ${returnedValue.runtimeType.toString()}, return_value: ${returnedValue.toString()}'; }; // This is a virtual location for test program to test [Location] functionality. @@ -60,14 +63,16 @@ void main() async { webF = WebF( viewportWidth: 360, viewportHeight: 640, - bundle: WebFBundle.fromContent('console.log("Starting integration tests...")', url: specUrl), + bundle: WebFBundle.fromContent( + 'console.log("Starting integration tests...")', + url: specUrl), disableViewportWidthAssertion: true, disableViewportHeightAssertion: true, javaScriptChannel: javaScriptChannel, gestureListener: GestureListener( onDrag: (GestureEvent gestureEvent) { if (gestureEvent.state == EVENT_STATE_START) { - var event = CustomEvent('nativegesture', CustomEventInit(detail: 'nativegesture')); + var event = CustomEvent('nativegesture', detail: 'nativegesture'); webF.controller!.view.document.documentElement?.dispatchEvent(event); } }, @@ -94,10 +99,9 @@ void main() async { testTextInput = TestTextInput(); WidgetsBinding.instance.addPostFrameCallback((_) async { - registerDartTestMethodsToCpp(); int contextId = webF.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 be3df35167..2ee141b133 100644 --- a/integration_tests/lib/plugin.dart +++ b/integration_tests/lib/plugin.dart @@ -53,24 +53,17 @@ void main() async { // Set render font family AlibabaPuHuiTi to resolve rendering difference. CSSText.DEFAULT_FONT_FAMILY_FALLBACK = ['AlibabaPuHuiTi']; - File specs = File(path.join(testDirectory, '.specs/plugin.build.js')); - - List<Map<String, String>> allSpecsPayload = [ - {'filename': path.basename(specs.path), 'filepath': specs.path, 'code': specs.readAsStringSync()} - ]; - List<Widget> widgets = []; - - for (int i = 0; i < WEBF_NUM; i++) { - var webf = webfMap[i] = WebF( - viewportWidth: 360, - viewportHeight: 640, - bundle: WebFBundle.fromContent('console.log("Starting Plugin tests...")'), - disableViewportWidthAssertion: true, - disableViewportHeightAssertion: true, - uriParser: IntegrationTestUriParser(), - ); - widgets.add(webf); - } + final String specTarget = '.specs/plugin.build.js'; + final File spec = File(path.join(testDirectory, specTarget)); + + late WebF webF = WebF( + viewportWidth: 360, + viewportHeight: 640, + bundle: WebFBundle.fromContent('console.log("Starting Plugin tests...")'), + disableViewportWidthAssertion: true, + disableViewportHeightAssertion: true, + uriParser: IntegrationTestUriParser(), + ); runApp(MaterialApp( title: 'WebF Plugin Tests', @@ -78,42 +71,25 @@ void main() async { home: Scaffold( appBar: AppBar(title: Text('WebF Plugin Tests')), body: Wrap( - children: widgets, + children: [ + webF + ], ), ), )); WidgetsBinding.instance.addPostFrameCallback((_) async { - registerDartTestMethodsToCpp(); - - List<Future<String>> testResults = []; - - for (int i = 0; i < widgets.length; i++) { - int contextId = i; - initTestFramework(contextId); - addJSErrorListener(contextId, (String err) { - print(err); - }); - - Map<String, String> payload = allSpecsPayload[i]; - - // Preload load test cases - String filename = payload['filename']!; - String code = payload['code']!; - evaluateTestScripts(contextId, code, url: filename); - - testResults.add(executeTest(contextId)); - } - - List<String> results = await Future.wait(testResults); - - for (int i = 0; i < results.length; i++) { - String status = results[i]; - if (status == 'failed') { - exit(1); - } - } - - exit(0); + int contextId = webF.controller!.view.contextId; + initTestFramework(contextId); + registerDartTestMethodsToCpp(contextId); + addJSErrorListener(contextId, print); + + // Preload load test cases + String code = spec.readAsStringSync(); + evaluateTestScripts(contextId, code, url: 'assets://plugin.js'); + String result = await executeTest(contextId); + // Manual dispose context for memory leak check. + disposePage(webF.controller!.view.contextId); + exit(result == 'failed' ? 1 : 0); }); } diff --git a/integration_tests/lib/test_module.dart b/integration_tests/lib/test_module.dart new file mode 100644 index 0000000000..01db3c088d --- /dev/null +++ b/integration_tests/lib/test_module.dart @@ -0,0 +1,62 @@ +import 'dart:async'; +import 'package:webf/webf.dart'; +import 'package:webf/dom.dart'; + +class DemoModule extends BaseModule { + DemoModule(ModuleManager? moduleManager) : super(moduleManager); + + @override + String get name => "Demo"; + + @override + dynamic invoke(String method, params, InvokeModuleCallback callback) { + switch (method) { + case 'noParams': + assert(params == null); + return true; + case 'callInt': + assert(params is int); + return params + params; + case 'callDouble': + assert(params is double); + return (params as double) + params; + case 'callString': + assert(params == 'helloworld'); + return (params as String).toUpperCase(); + case 'callArray': + assert(params is List); + return (params as List).reduce((e, i) => e + i); + case 'callNull': + return null; + case 'callObject': + assert(params is Map); + return params['value']; + case 'callAsyncFn': + Timer(Duration(milliseconds: 100), () async { + dynamic returnValue = await callback(data: [ + 1, + '2', + null, + 4.0, + {'value': 1} + ]); + assert(returnValue == 'success'); + }); + return params; + case 'callAsyncFnFail': + Timer(Duration(milliseconds: 100), () async { + dynamic returnValue = await callback(error: 'Must to fail'); + assert(returnValue == 'fail'); + }); + return null; + case 'callToDispatchEvent': + CustomEvent customEvent = CustomEvent('click', detail: 'helloworld'); + dynamic result = + dispatchEvent(event: customEvent, data: [1, 2, 3, 4, 5]); + assert(result == 'success'); + } + } + + @override + void dispose() {} +} diff --git a/integration_tests/pubspec.yaml b/integration_tests/pubspec.yaml index 37fd04a5c3..4ceaa5ebe5 100644 --- a/integration_tests/pubspec.yaml +++ b/integration_tests/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: flutter: sdk: flutter image: ^3.0.2 - webf_websocket: ^1.0.0 + webf_websocket: ^1.1.0-beta.2 waterfall_flow: ^3.0.1 image_compare: ^1.1.2 diff --git a/integration_tests/runtime/kraken.d.ts b/integration_tests/runtime/kraken.d.ts index 54e08e7034..971912f610 100644 --- a/integration_tests/runtime/kraken.d.ts +++ b/integration_tests/runtime/kraken.d.ts @@ -1,14 +1,17 @@ /* * Copyright (C) 2022-present The Kraken authors. All rights reserved. */ -type MethodHandler = (method: string, args: any[]) => void; +type MethodCallHandler = (args: any) => any; interface MethodChannel { - addMethodCallHandler(handler: MethodHandler): void; - removeMethodCallHandler(handler: MethodHandler): void; + addMethodCallHandler(method: string, handler: MethodCallHandler): void; + removeMethodCallHandler(method: string): void; + clearMethodCallHandler(): void; invokeMethod(method: string, ...args: any[]): Promise<string> } interface WebF { + invokeModule: (module: string, method: string, params?: any | null, fn?: (err: Error, data: any) => any) => any; + addWebfModuleListener: (moduleName: string, fn: (event: Event, extra: any) => any) => void; methodChannel: MethodChannel; } diff --git a/integration_tests/scripts/core_integration_starter.js b/integration_tests/scripts/core_integration_starter.js index 7205b62884..1ffa645cf7 100644 --- a/integration_tests/scripts/core_integration_starter.js +++ b/integration_tests/scripts/core_integration_starter.js @@ -28,10 +28,10 @@ function startIntegrationTest() { const tester = spawn(testExecutable, [], { env: { ...process.env, - KRAKEN_ENABLE_TEST: 'true', + WEBF_ENABLE_TEST: 'true', 'enable-software-rendering': true, 'skia-deterministic-rendering': true, - KRAKEN_TEST_DIR: path.join(__dirname, '../') + WEBF_TEST_DIR: path.join(__dirname, '../') }, cwd: process.cwd(), stdio: 'inherit' @@ -41,10 +41,14 @@ function startIntegrationTest() { process.exit(code); }); tester.on('error', (error) => { - console.error(error); + console.error('integration failed', error); process.exit(1); }); tester.on('exit', (code, signal) => { + if (signal) { + console.log('Process exit with ' + signal); + process.exit(1); + } if (code != 0) { process.exit(1); } diff --git a/integration_tests/scripts/html_loader.js b/integration_tests/scripts/html_loader.js index 0ae1c7e683..3c18187b24 100644 --- a/integration_tests/scripts/html_loader.js +++ b/integration_tests/scripts/html_loader.js @@ -41,10 +41,14 @@ const loader = function(source) { // Set attr of HTML can let the case use fit. For example: <html fit> xxx </html>. let isFit = false; + let isXit = false; root.childNodes && root.childNodes.forEach(ele => { if (ele.rawAttrs && ele.rawAttrs.indexOf('fit') >= 0) { isFit = true; } + if (ele.rawAttrs && ele.rawAttrs.indexOf('xit') >= 0) { + isXit = true; + } }) const htmlString = root.toString().replace(/['\n]/g, function(c){ @@ -57,7 +61,7 @@ const loader = function(source) { const html_parse = () => __webf_parse_html__('${htmlString}'); var index = 0; const snapshotAction = async () => { await snapshot(null, '${snapshotFilepath}', ${scripts.length === 0 ? 'null' : 'index.toString()'}); index++; }; - ${isFit ? 'fit' : 'it'}("should work", async (done) => {\ + ${isFit ? 'fit' : isXit ? 'xit' : 'it'}("should work", async (done) => {\ html_parse();\ requestAnimationFrame(async () => { ${scripts.length === 0 ? `await snapshotAction();` : scripts.join('\n')} diff --git a/integration_tests/scripts/plugin_integration_starter.js b/integration_tests/scripts/plugin_integration_starter.js index 87da50a816..eb0c5c14ea 100644 --- a/integration_tests/scripts/plugin_integration_starter.js +++ b/integration_tests/scripts/plugin_integration_starter.js @@ -20,8 +20,8 @@ function startIntegrationTest() { const tester = spawn(testExecutable, [], { env: { ...process.env, - KRAKEN_ENABLE_TEST: 'true', - KRAKEN_TEST_DIR: path.join(__dirname, '../') + WEBF_ENABLE_TEST: 'true', + WEBF_TEST_DIR: path.join(__dirname, '../') }, cwd: process.cwd(), stdio: 'inherit' diff --git a/integration_tests/snapshots/css/css-color/hsl-001.html.65645d7c1.png b/integration_tests/snapshots/css/css-color/hsl-001.html.65645d7c1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-001.html.65645d7c1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-002.html.797a2f5a1.png b/integration_tests/snapshots/css/css-color/hsl-002.html.797a2f5a1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-002.html.797a2f5a1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-003.html.ac8782251.png b/integration_tests/snapshots/css/css-color/hsl-003.html.ac8782251.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-003.html.ac8782251.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-004.html.972de9c51.png b/integration_tests/snapshots/css/css-color/hsl-004.html.972de9c51.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-004.html.972de9c51.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-005.html.b1402b061.png b/integration_tests/snapshots/css/css-color/hsl-005.html.b1402b061.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-005.html.b1402b061.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-006.html.cc1c51e11.png b/integration_tests/snapshots/css/css-color/hsl-006.html.cc1c51e11.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-006.html.cc1c51e11.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-007.html.8d366c2f1.png b/integration_tests/snapshots/css/css-color/hsl-007.html.8d366c2f1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-007.html.8d366c2f1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsl-008.html.0ef86b9b1.png b/integration_tests/snapshots/css/css-color/hsl-008.html.0ef86b9b1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsl-008.html.0ef86b9b1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-001.html.36c76bb61.png b/integration_tests/snapshots/css/css-color/hsla-001.html.36c76bb61.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-001.html.36c76bb61.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-002.html.0718b4031.png b/integration_tests/snapshots/css/css-color/hsla-002.html.0718b4031.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-002.html.0718b4031.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-003.html.6088464b1.png b/integration_tests/snapshots/css/css-color/hsla-003.html.6088464b1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-003.html.6088464b1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-004.html.84d8420e1.png b/integration_tests/snapshots/css/css-color/hsla-004.html.84d8420e1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-004.html.84d8420e1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-005.html.d3a946911.png b/integration_tests/snapshots/css/css-color/hsla-005.html.d3a946911.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-005.html.d3a946911.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-006.html.b259b3691.png b/integration_tests/snapshots/css/css-color/hsla-006.html.b259b3691.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-006.html.b259b3691.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-007.html.f58df36c1.png b/integration_tests/snapshots/css/css-color/hsla-007.html.f58df36c1.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-007.html.f58df36c1.png differ diff --git a/integration_tests/snapshots/css/css-color/hsla-008.html.be9b8de71.png b/integration_tests/snapshots/css/css-color/hsla-008.html.be9b8de71.png new file mode 100644 index 0000000000..313517a0d7 Binary files /dev/null and b/integration_tests/snapshots/css/css-color/hsla-008.html.be9b8de71.png differ diff --git a/integration_tests/snapshots/css/css-display/containing-block.ts.1224b51c1.png b/integration_tests/snapshots/css/css-display/containing-block.ts.1224b51c1.png new file mode 100644 index 0000000000..a3b4abb396 Binary files /dev/null and b/integration_tests/snapshots/css/css-display/containing-block.ts.1224b51c1.png differ diff --git a/integration_tests/snapshots/css/css-display/containing-block.ts.257a6dbe1.png b/integration_tests/snapshots/css/css-display/containing-block.ts.257a6dbe1.png new file mode 100644 index 0000000000..f1e3c3a2bc Binary files /dev/null and b/integration_tests/snapshots/css/css-display/containing-block.ts.257a6dbe1.png differ diff --git a/integration_tests/snapshots/css/css-display/containing-block.ts.2bc3ae9b1.png b/integration_tests/snapshots/css/css-display/containing-block.ts.2bc3ae9b1.png new file mode 100644 index 0000000000..f1e3c3a2bc Binary files /dev/null and b/integration_tests/snapshots/css/css-display/containing-block.ts.2bc3ae9b1.png differ diff --git a/integration_tests/snapshots/css/css-display/containing-block.ts.41db82851.png b/integration_tests/snapshots/css/css-display/containing-block.ts.41db82851.png new file mode 100644 index 0000000000..80bc548779 Binary files /dev/null and b/integration_tests/snapshots/css/css-display/containing-block.ts.41db82851.png differ diff --git a/integration_tests/snapshots/css/css-display/containing-block.ts.ab73825c1.png b/integration_tests/snapshots/css/css-display/containing-block.ts.ab73825c1.png new file mode 100644 index 0000000000..f0b2a538bc Binary files /dev/null and b/integration_tests/snapshots/css/css-display/containing-block.ts.ab73825c1.png differ diff --git a/integration_tests/snapshots/css/css-display/containing-block.ts.b6c95ac21.png b/integration_tests/snapshots/css/css-display/containing-block.ts.b6c95ac21.png new file mode 100644 index 0000000000..80bc548779 Binary files /dev/null and b/integration_tests/snapshots/css/css-display/containing-block.ts.b6c95ac21.png differ diff --git a/integration_tests/snapshots/css/css-display/containing-block.ts.bea921441.png b/integration_tests/snapshots/css/css-display/containing-block.ts.bea921441.png new file mode 100644 index 0000000000..a3b4abb396 Binary files /dev/null and b/integration_tests/snapshots/css/css-display/containing-block.ts.bea921441.png differ diff --git a/integration_tests/snapshots/css/css-display/containing-block.ts.f2e1ab071.png b/integration_tests/snapshots/css/css-display/containing-block.ts.f2e1ab071.png new file mode 100644 index 0000000000..f1e3c3a2bc Binary files /dev/null and b/integration_tests/snapshots/css/css-display/containing-block.ts.f2e1ab071.png differ diff --git a/integration_tests/snapshots/dom/elements/link.ts.2b607cce1.png b/integration_tests/snapshots/dom/elements/link.ts.2b607cce1.png new file mode 100644 index 0000000000..95a4a862e3 Binary files /dev/null and b/integration_tests/snapshots/dom/elements/link.ts.2b607cce1.png differ diff --git a/integration_tests/spec_group.json b/integration_tests/spec_group.json index aeb69201e1..1d2a1d93f7 100644 --- a/integration_tests/spec_group.json +++ b/integration_tests/spec_group.json @@ -6,7 +6,7 @@ "specs/blob/**/*.{js,jsx,ts,tsx,html}", "specs/cookie/**/*.{js,jsx,ts,tsx,html}", "specs/fetch/**/*.{js,jsx,ts,tsx,html}", - "specs/method-channel/**/*.{js,jsx,ts,tsx,html}", + "specs/modules/**/*.{js,jsx,ts,tsx,html}", "specs/navigator/**/*.{js,jsx,ts,tsx,html}", "specs/performance/**/*.{js,jsx,ts,tsx,html}", "specs/timer/**/*.{js,jsx,ts,tsx,html}", diff --git a/integration_tests/specs/cookie/cookie.ts b/integration_tests/specs/cookie/cookie.ts index 8a05baedb9..1781fe1be9 100644 --- a/integration_tests/specs/cookie/cookie.ts +++ b/integration_tests/specs/cookie/cookie.ts @@ -1,8 +1,8 @@ describe('Cookie', () => { it('works with cookie getter and setter', async () => { + expect(document.cookie).toBe(''); document.cookie = "name=oeschger"; document.cookie = "favorite_food=tripe"; - document.body.appendChild(document.createTextNode(document.cookie)); - await snapshot(); + expect(document.cookie).toBe('name=oeschger; favorite_food=tripe'); }); }); diff --git a/integration_tests/specs/css/css-animations/animation-delay-001-manual.html b/integration_tests/specs/css/css-animations/animation-delay-001-manual.html index 5dcce6b80a..615eaffb43 100644 --- a/integration_tests/specs/css/css-animations/animation-delay-001-manual.html +++ b/integration_tests/specs/css/css-animations/animation-delay-001-manual.html @@ -24,15 +24,16 @@ #test-negative-delay { animation-name: test-negative-delay; - animation-duration: 4s; - animation-delay: -2s; + animation-duration: 6s; + animation-delay: -3s; background-color: blue; } #ref-no-delay { animation-name: ref-no-delay; - animation-duration: 2s; + + animation-duration: 3s; background-color: yellow; } @@ -64,10 +65,15 @@ <div id="ref-no-delay">Filler Text</div> </body> <script> - await nextFrames(); - await snapshotAction(); - await nextFrames(20); - await snapshotAction(); - await nextFrames(20); - await snapshotAction(); + await sleep(0.1); + const negativeDelay = document.getElementById('test-negative-delay'); + const ref = document.getElementById('ref-no-delay'); + expect(Math.round(negativeDelay.offsetLeft)).toBe(Math.round(ref.offsetLeft)); + expect(negativeDelay.offsetLeft < 100 && negativeDelay.offsetLeft > 70).toBe(true); + await sleep(1); + expect(negativeDelay.offsetLeft).toBe(ref.offsetLeft); + expect(negativeDelay.offsetLeft < 60 && negativeDelay.offsetLeft > 20).toBe(true); + await sleep(3); + expect(negativeDelay.offsetLeft).toBe(ref.offsetLeft); + expect(negativeDelay.offsetLeft === 0).toBe(true); </script> \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-delay-002-manual.html b/integration_tests/specs/css/css-animations/animation-delay-002-manual.html index 572a78cfdc..da4b530e14 100644 --- a/integration_tests/specs/css/css-animations/animation-delay-002-manual.html +++ b/integration_tests/specs/css/css-animations/animation-delay-002-manual.html @@ -30,16 +30,16 @@ which starts moving from right to left after about 5 seconds from the time the page is loaded. </p> - <div>Filler Text</div> + <div id="main">Filler Text</div> </body> <script> - await nextFrames(); - await snapshotAction(); + await sleep(0.1); + const main = document.getElementById('main'); + expect(main.offsetLeft).toBe(0); + await sleep(1); - await nextFrames(); - await snapshotAction(); - await nextFrames(20); - await snapshotAction(); - await nextFrames(50); - await snapshotAction(); + expect(main.offsetLeft <= 150 && main.offsetLeft > 100).toBe(true); + + await sleep(3); + expect(main.offsetLeft === 0).toBe(true); </script> \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-delay-003-manual.html b/integration_tests/specs/css/css-animations/animation-delay-003-manual.html index f0672fbd92..6c024e0bc1 100644 --- a/integration_tests/specs/css/css-animations/animation-delay-003-manual.html +++ b/integration_tests/specs/css/css-animations/animation-delay-003-manual.html @@ -29,11 +29,14 @@ Test passes if there is a filled blue square with 'Filler Text', which starts moving from right to left as soon as the page loads. </p> - <div>Filler Text</div> + <div id="animation-box">Filler Text</div> </body> <script> - await sleep(0.02); - await snapshotAction(); + await sleep(0.1); + let animationBox = document.getElementById('animation-box'); + expect(animationBox.offsetLeft < 150 && animationBox.offsetLeft > 120).toBe(true); + await sleep(1); + expect(animationBox.offsetLeft > 50 && animationBox.offsetLeft < 75).toBe(true); await sleep(3); - await snapshotAction(); + expect(animationBox.offsetLeft === 0).toBe(true); </script> diff --git a/integration_tests/specs/css/css-animations/animation-direction-001-manual.html b/integration_tests/specs/css/css-animations/animation-direction-001-manual.html index 7b007507f8..d3889eeef5 100644 --- a/integration_tests/specs/css/css-animations/animation-direction-001-manual.html +++ b/integration_tests/specs/css/css-animations/animation-direction-001-manual.html @@ -45,13 +45,14 @@ which starts moving from right to left on the page load, and then moves from left to right. This cycle gets repeated. </p> - <div>Filler Text</div> + <div id="main">Filler Text</div> </body> <script> - await nextFrames(); - await snapshotAction(); - await nextFrames(20); - await snapshotAction(); - await nextFrames(20); - await snapshotAction(); + await sleep(0.1); + const main = document.getElementById('main'); + expect(main.offsetLeft > 120 && main.offsetLeft < 150).toBe(true); + await sleep(2); + expect(main.offsetLeft > 0 && main.offsetLeft < 75).toBe(true); + await sleep(2); + expect(main.offsetLeft > 100 && main.offsetLeft < 150).toBe(true); </script> \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-direction-002-manual.html b/integration_tests/specs/css/css-animations/animation-direction-002-manual.html index b3e3c34ca0..e8ba43db9b 100644 --- a/integration_tests/specs/css/css-animations/animation-direction-002-manual.html +++ b/integration_tests/specs/css/css-animations/animation-direction-002-manual.html @@ -39,13 +39,16 @@ which starts moving from right to left, then back to right and moves from right to left again. This cycle gets repeated. </p> - <div>Filler Text</div> + <div id="main">Filler Text</div> </body> <script> - await nextFrames(); - await snapshotAction(); - await nextFrames(20); - await snapshotAction(); - await nextFrames(20); - await snapshotAction(); + await sleep(0.1); + const main = document.getElementById('main'); + expect(main.offsetLeft > 100 && main.offsetLeft < 150).toBe(true); + await sleep(1); + expect(main.offsetLeft < 75 && main.offsetLeft > 0).toBe(true); + await sleep(1); + expect(main.offsetLeft > 100 && main.offsetLeft < 150).toBe(true); + await sleep(1); + expect(main.offsetLeft < 75 && main.offsetLeft > 0).toBe(true); </script> \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-direction-003-manual.html b/integration_tests/specs/css/css-animations/animation-direction-003-manual.html index b736221d79..12dacfe937 100644 --- a/integration_tests/specs/css/css-animations/animation-direction-003-manual.html +++ b/integration_tests/specs/css/css-animations/animation-direction-003-manual.html @@ -1,5 +1,5 @@ <!DOCTYPE html> -<meta charset="utf-8"> +<meta charset="utf-8" fit="true"> <title>CSS Animations Test: animation-direction - alternate-reverse @@ -41,13 +41,16 @@ which starts moving from right to left on the page load, and then moves from left to right. This cycle gets repeated.

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

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

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

Filler Text
diff --git a/integration_tests/specs/css/css-animations/animation-duration-005-manual.html b/integration_tests/specs/css/css-animations/animation-duration-005-manual.html index bc4fee621d..4007302ed5 100644 --- a/integration_tests/specs/css/css-animations/animation-duration-005-manual.html +++ b/integration_tests/specs/css/css-animations/animation-duration-005-manual.html @@ -1,5 +1,5 @@ - + CSS Animations Test: animation-duration - 0s, animation-fill-mode - forwards @@ -49,7 +49,9 @@ \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-duration-006-manual.html b/integration_tests/specs/css/css-animations/animation-duration-006-manual.html index fce51d5852..31029469c4 100644 --- a/integration_tests/specs/css/css-animations/animation-duration-006-manual.html +++ b/integration_tests/specs/css/css-animations/animation-duration-006-manual.html @@ -1,5 +1,5 @@ - + CSS Animations Test: animation-duration - 0s, animation-fill-mode - both diff --git a/integration_tests/specs/css/css-display/containing-block.ts b/integration_tests/specs/css/css-display/containing-block.ts index c413d83d3e..9488fc7c07 100644 --- a/integration_tests/specs/css/css-display/containing-block.ts +++ b/integration_tests/specs/css/css-display/containing-block.ts @@ -1632,4 +1632,323 @@ describe('containing-block', () => { await snapshot(); }); + + it('percent-margin-bottom', async () => { + let p; + let container; + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`There should be a blue square below.`)] + ); + container = createElement( + 'div', + { + id: 'container', + 'data-expected-width': '100', + 'data-expected-height': '100', + style: { + overflow: 'hidden', + background: 'blue', + 'box-sizing': 'border-box', + width: '100px', + }, + }, + [ + createElement('div', { + style: { + 'margin-bottom': '50%', + height: '50px', + 'box-sizing': 'border-box', + }, + }), + ] + ); + BODY.appendChild(p); + BODY.appendChild(container); + + await snapshot(); + }); + it('percent-margin-left', async () => { + let p; + let container; + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`There should be a blue square below.`)] + ); + container = createElement( + 'div', + { + id: 'container', + style: { + 'box-sizing': 'border-box', + width: '200px', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-expected-height': '100', + style: { + 'margin-left': '50%', + height: '100px', + background: 'blue', + 'box-sizing': 'border-box', + }, + }), + ] + ); + BODY.appendChild(p); + BODY.appendChild(container); + + await snapshot(); + }); + it('percent-margin-right', async () => { + let p; + let container; + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`There should be a blue square below.`)] + ); + container = createElement( + 'div', + { + id: 'container', + style: { + 'box-sizing': 'border-box', + width: '200px', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-expected-height': '100', + style: { + 'margin-right': '50%', + height: '100px', + background: 'blue', + 'box-sizing': 'border-box', + }, + }), + ] + ); + BODY.appendChild(p); + BODY.appendChild(container); + + await snapshot(); + }); + it('percent-margin-top', async () => { + let p; + let container; + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`There should be a blue square below.`)] + ); + container = createElement( + 'div', + { + id: 'container', + 'data-expected-width': '100', + 'data-expected-height': '100', + style: { + overflow: 'hidden', + background: 'blue', + 'box-sizing': 'border-box', + width: '100px', + }, + }, + [ + createElement('div', { + style: { + 'margin-top': '50%', + height: '50px', + 'box-sizing': 'border-box', + }, + }), + ] + ); + BODY.appendChild(p); + BODY.appendChild(container); + + await snapshot(); + }); + it('percent-padding-bottom', async () => { + let p; + let container; + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`There should be a blue square below.`)] + ); + container = createElement( + 'div', + { + id: 'container', + style: { + 'box-sizing': 'border-box', + width: '500px', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-expected-height': '100', + style: { + 'padding-bottom': '10%', + width: '100px', + height: '50px', + background: 'blue', + 'box-sizing': 'border-box', + }, + }), + ] + ); + BODY.appendChild(p); + BODY.appendChild(container); + + await snapshot(); + }); + it('percent-padding-left', async () => { + let p; + let container; + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`There should be a blue square below.`)] + ); + container = createElement( + 'div', + { + id: 'container', + style: { + 'box-sizing': 'border-box', + width: '500px', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-expected-height': '100', + style: { + 'padding-left': '10%', + width: '50px', + height: '100px', + background: 'blue', + 'box-sizing': 'border-box', + }, + }), + ] + ); + BODY.appendChild(p); + BODY.appendChild(container); + + await snapshot(); + }); + it('percent-padding-right', async () => { + let p; + let container; + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`There should be a blue square below.`)] + ); + container = createElement( + 'div', + { + id: 'container', + style: { + 'box-sizing': 'border-box', + width: '500px', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-expected-height': '100', + style: { + 'padding-right': '10%', + width: '50px', + height: '100px', + background: 'blue', + 'box-sizing': 'border-box', + }, + }), + ] + ); + BODY.appendChild(p); + BODY.appendChild(container); + + await snapshot(); + }); + it('percent-padding-top', async () => { + let p; + let container; + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`There should be a blue square below.`)] + ); + container = createElement( + 'div', + { + id: 'container', + style: { + 'box-sizing': 'border-box', + width: '500px', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-expected-height': '100', + style: { + 'padding-top': '10%', + width: '100px', + height: '50px', + background: 'blue', + 'box-sizing': 'border-box', + }, + }), + ] + ); + BODY.appendChild(p); + BODY.appendChild(container); + + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-flow/containing-block.ts b/integration_tests/specs/css/css-flow/containing-block.ts deleted file mode 100644 index 21dd8471e8..0000000000 --- a/integration_tests/specs/css/css-flow/containing-block.ts +++ /dev/null @@ -1,1863 +0,0 @@ -/*auto generated*/ -describe('containing-block', () => { - it('001', async () => { - let p; - let div1; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if there is a filled green square and `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`no red`)] - ), - createText(`.`), - ] - ); - div1 = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - id: 'div1', - style: { - background: 'red', - display: 'block', - height: '100px', - width: '100px', - position: 'relative', - 'box-sizing': 'border-box', - }, - }, - [ - createElement('div', { - style: { - background: 'green', - height: '100%', - position: 'relative', - width: '100%', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div1); - - await snapshot(); - }); - it('003', async () => { - let div1; - div1 = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - id: 'div1', - style: { - background: 'red', - display: 'inline-block', - height: '60px', - padding: '20px', - width: '60px', - left: '-20px', - position: 'relative', - top: '-20px', - 'box-sizing': 'border-box', - }, - }, - [ - createElement('div', { - style: { - background: 'green', - height: '100px', - left: '-20px', - position: 'relative', - top: '-20px', - width: '100px', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(div1); - - await snapshot(); - }); - it('004', async () => { - let p; - let div1; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if there is a filled green square and `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`no red`)] - ), - createText(`.`), - ] - ); - div1 = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - id: 'div1', - style: { - background: 'red', - display: 'block', - height: '100px', - width: '100px', - position: 'static', - 'box-sizing': 'border-box', - }, - }, - [ - createElement('div', { - style: { - background: 'green', - height: '100%', - position: 'static', - width: '100%', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div1); - - await snapshot(); - }); - it('006', async () => { - let p; - let div1; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if there is a filled green square and `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`no red`)] - ), - createText(`.`), - ] - ); - div1 = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - id: 'div1', - style: { - background: 'red', - display: 'inline-block', - height: '100px', - width: '100px', - position: 'static', - 'box-sizing': 'border-box', - }, - }, - [ - createElement('div', { - style: { - background: 'green', - height: '100%', - position: 'static', - width: '100%', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div1); - - await snapshot(); - }); - it('007-ref', async () => { - let p; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: {}, - }, - [ - createText(`Test passes if there is a filled blue square in the upper-right corner of the - page.`), - createElement('img', { - src: 'assets/blue15x15.png', - width: '96', - height: '96', - alt: 'Image download support must be enabled', - style: { - position: 'absolute', - right: '0px', - top: '0px', - }, - }), - ] - ); - BODY.appendChild(p); - - await snapshot(0.1); - }); - it('007', async () => { - let p; - let div1; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText( - `Test passes if there is a filled blue square in the upper-right corner of the page.` - ), - ] - ); - div1 = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - id: 'div1', - style: { - position: 'relative', - bottom: '0', - }, - }, - [ - createElement('div', { - style: { - background: 'blue', - height: '100px', - position: 'fixed', - right: '0', - top: '0', - width: '100px', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div1); - - await snapshot(); - }); - it('008-ref', async () => { - let p; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: {}, - }, - [ - createText(`Test passes if a filled blue square is in the `), - createElement( - 'strong', - { - style: {}, - }, - [createText(`upper-right corner`)] - ), - createText(` of an hollow black square.`), - ] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - border: '10px solid black', - height: '196px', - 'margin-left': '50px', - position: 'absolute', - top: '50px', - width: '196px', - }, - }, - [ - createElement('img', { - src: 'assets/blue15x15.png', - width: '96', - height: '96', - alt: 'Image download support must be enabled', - style: { - position: 'relative', - left: '100px', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(0.2); - }); - it('008', async () => { - let p; - let div3; - let div2; - let div1; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if a filled blue square is in the `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`upper-right corner`)] - ), - createText(` of an hollow black square.`), - ] - ); - div1 = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - id: 'div1', - style: { - border: '2px solid black', - margin: '50px', - position: 'absolute', - top: '0', - height: '100px', - width: '100px', - 'box-sizing': 'border-box', - }, - }, - [ - (div2 = createElement( - 'div', - { - id: 'div2', - style: { - height: '100px', - width: '100px', - margin: '50px', - 'box-sizing': 'border-box', - }, - }, - [ - (div3 = createElement('div', { - id: 'div3', - style: { - height: '100px', - width: '100px', - background: 'blue', - right: '0', - position: 'absolute', - top: '0', - 'box-sizing': 'border-box', - }, - })), - ] - )), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div1); - - await snapshot(); - }); - it('009-ref', async () => { - let div; - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - border: '2px solid black', - height: '196px', - margin: '50px', - 'text-align': 'right', - }, - }, - [ - createElement('img', { - src: 'assets/blue96x96.png', - width: '96', - height: '96', - alt: 'Image download support must be enabled', - style: {}, - }), - ] - ); - BODY.appendChild(div); - - await snapshot(0.1); - }); - it('009', async () => { - let p; - let div3; - let div2; - let div1; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if a filled blue square is in the `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`upper-right corner`)] - ), - createText(` of an hollow wide black rectangle.`), - ] - ); - div1 = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - id: 'div1', - style: { - border: '2px solid black', - margin: '50px', - position: 'relative', - top: '0', - 'box-sizing': 'border-box', - }, - }, - [ - (div2 = createElement( - 'div', - { - id: 'div2', - style: { - height: '100px', - width: '100px', - margin: '50px', - 'box-sizing': 'border-box', - }, - }, - [ - (div3 = createElement('div', { - id: 'div3', - style: { - height: '100px', - width: '100px', - background: 'blue', - right: '0', - position: 'absolute', - top: '0', - 'box-sizing': 'border-box', - }, - })), - ] - )), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div1); - - await snapshot(); - }); - it('010', async () => { - let p; - let div3; - let div2; - let div1; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if a filled blue square is in the `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`upper-right corner`)] - ), - createText(` of an hollow black square.`), - ] - ); - div1 = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - id: 'div1', - style: { - border: '2px solid black', - margin: '50px', - position: 'fixed', - top: '0', - 'box-sizing': 'border-box', - }, - }, - [ - (div2 = createElement( - 'div', - { - id: 'div2', - style: { - height: '100px', - width: '100px', - margin: '50px', - 'box-sizing': 'border-box', - }, - }, - [ - (div3 = createElement('div', { - id: 'div3', - style: { - background: 'blue', - right: '0', - position: 'absolute', - top: '0', - height: '100px', - width: '100px', - 'box-sizing': 'border-box', - }, - })), - ] - )), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div1); - - await snapshot(); - }); - it('011', async () => { - let p; - let span1; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if the filled blue square is in the `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`lower-right corner`)] - ), - createText(` of the hollow black square.`), - ] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - border: '2px solid black', - padding: '100px', - position: 'relative', - width: '0', - 'box-sizing': 'border-box', - }, - }, - [ - (span1 = createElement( - 'span', - { - id: 'span1', - style: { - direction: 'ltr', - 'box-sizing': 'border-box', - }, - }, - [ - createElement('span', { - style: { - background: 'blue', - height: '100px', - position: 'absolute', - width: '100px', - 'box-sizing': 'border-box', - }, - }), - ] - )), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(); - }); - it('013', async () => { - let p; - let span1; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if the filled blue square is in the `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`lower-right corner`)] - ), - createText(` of the hollow black square.`), - ] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - border: '2px solid black', - padding: '100px', - position: 'absolute', - width: '0', - 'box-sizing': 'border-box', - }, - }, - [ - (span1 = createElement( - 'span', - { - id: 'span1', - style: { - direction: 'ltr', - 'box-sizing': 'border-box', - }, - }, - [ - createElement('span', { - style: { - background: 'blue', - height: '100px', - position: 'absolute', - width: '100px', - 'box-sizing': 'border-box', - }, - }), - ] - )), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(); - }); - it('015', async () => { - let p; - let span1; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if the filled blue square is in the `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`lower-right corner`)] - ), - createText(` of the hollow black square.`), - ] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - border: '2px solid black', - padding: '100px', - position: 'fixed', - width: '0', - 'box-sizing': 'border-box', - }, - }, - [ - (span1 = createElement( - 'span', - { - id: 'span1', - style: { - direction: 'ltr', - 'box-sizing': 'border-box', - }, - }, - [ - createElement('span', { - style: { - background: 'blue', - height: '100px', - position: 'absolute', - width: '100px', - 'box-sizing': 'border-box', - }, - }), - ] - )), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(); - }); - - // @TODO: Height of display: inline element is wrong. - xit('017', async () => { - let p; - let tlControl; - let firstBox; - let position; - let position_1; - let brControl; - let lastBox; - let test; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`Test passes if there is no red visible on the page.`)] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - border: '2px solid silver', - direction: 'ltr', - 'margin-bottom': '20px', - padding: '100px', - width: '450px', - 'box-sizing': 'border-box', - }, - }, - [ - (test = createElement( - 'span', - { - id: 'test', - style: { - border: '5px solid silver', - padding: '50px', - position: 'relative', - 'box-sizing': 'border-box', - }, - }, - [ - (firstBox = createElement( - 'span', - { - id: 'first-box', - style: { - color: 'silver', - 'box-sizing': 'border-box', - }, - }, - [ - (tlControl = createElement('span', { - id: 'tl-control', - style: { - 'border-top': '30px solid red', - 'margin-left': '-50px', - 'margin-right': '20px', - padding: '20px 15px', - 'box-sizing': 'border-box', - }, - })), - createText(`Filler Text Filler Text Filler Text Filler Text`), - ] - )), - (position = createElement( - 'span', - { - class: 'position bottom-right', - style: { - height: '30px', - position: 'absolute', - width: '30px', - background: 'green', - bottom: '0', - right: '0', - 'box-sizing': 'border-box', - }, - }, - [createText(`BR`)] - )), - (position_1 = createElement( - 'span', - { - class: 'position top-left', - style: { - height: '30px', - position: 'absolute', - width: '30px', - background: 'green', - left: '0', - top: '0', - 'box-sizing': 'border-box', - }, - }, - [createText(`TL`)] - )), - (lastBox = createElement( - 'span', - { - id: 'last-box', - style: { - color: 'silver', - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Filler Text Filler Text Filler Text Filler Text`), - (brControl = createElement('span', { - id: 'br-control', - style: { - 'border-bottom': '30px solid red', - 'margin-left': '20px', - 'margin-right': '-50px', - padding: '20px 15px', - 'box-sizing': 'border-box', - }, - })), - ] - )), - ] - )), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(); - }); - it('019-ref', async () => { - let div; - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - border: '2px solid black', - height: '96px', - 'padding-top': '96px', - 'text-align': 'right', - width: '192px', - }, - }, - [ - createElement('img', { - src: 'assets/blue96x96.png', - width: '96', - height: '96', - alt: 'Image download support must be enabled', - style: {}, - }), - ] - ); - BODY.appendChild(div); - - await snapshot(0.1); - }); - it('019', async () => { - let p; - let span1; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if a filled blue square is in the `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`lower-right corner`)] - ), - createText(` of an hollow black square.`), - ] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - border: '2px solid black', - padding: '100px', - position: 'absolute', - width: '0', - 'box-sizing': 'border-box', - }, - }, - [ - (span1 = createElement( - 'span', - { - id: 'span1', - style: { - display: 'block', - direction: 'ltr', - 'box-sizing': 'border-box', - }, - }, - [ - createElement('span', { - style: { - display: 'block', - background: 'blue', - height: '100px', - left: 'auto', - position: 'absolute', - top: 'auto', - width: '100px', - 'box-sizing': 'border-box', - }, - }), - ] - )), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(); - }); - it('020-ref', async (done) => { - let p; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: {}, - }, - [ - createText(`Test passes if a filled blue square is in the `), - createElement( - 'strong', - { - style: {}, - }, - [createText(`lower-left corner`)] - ), - createText(` of an hollow black square.`), - ] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - border: '2px solid black', - height: '96px', - 'padding-top': '96px', - width: '192px', - }, - }, - [ - createElement('img', { - src: 'assets/blue96x96.png', - width: '96', - height: '96', - alt: 'Image download support must be enabled', - style: {}, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(0.1); - done(); - }); - it('020', async (done) => { - let p; - let span1; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if a filled blue square is in the `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`lower-left corner`)] - ), - createText(` of an hollow black square.`), - ] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - border: '2px solid black', - padding: '100px', - position: 'absolute', - width: '0', - 'box-sizing': 'border-box', - }, - }, - [ - (span1 = createElement( - 'span', - { - id: 'span1', - style: { - display: 'block', - direction: 'rtl', - 'box-sizing': 'border-box', - }, - }, - [ - createElement('span', { - style: { - display: 'block', - background: 'blue', - height: '100px', - left: 'auto', - position: 'absolute', - top: 'auto', - width: '100px', - 'box-sizing': 'border-box', - }, - }), - ] - )), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(); - done(); - }); - it('021', async () => { - let p; - let span1; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if a filled blue square is in the `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`lower-right corner`)] - ), - createText(` of an hollow black square.`), - ] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - border: '2px solid black', - padding: '100px', - position: 'fixed', - width: '0', - 'box-sizing': 'border-box', - }, - }, - [ - (span1 = createElement( - 'span', - { - id: 'span1', - style: { - display: 'block', - direction: 'ltr', - 'box-sizing': 'border-box', - }, - }, - [ - createElement('span', { - style: { - display: 'block', - background: 'blue', - height: '100px', - left: 'auto', - position: 'absolute', - top: 'auto', - width: '100px', - 'box-sizing': 'border-box', - }, - }), - ] - )), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(); - }); - it('022', async () => { - let p; - let span1; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if a filled blue square is in the `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`lower-left corner`)] - ), - createText(` of an hollow black square.`), - ] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - border: '2px solid black', - padding: '100px', - position: 'fixed', - width: '0', - 'box-sizing': 'border-box', - }, - }, - [ - (span1 = createElement( - 'span', - { - id: 'span1', - style: { - display: 'block', - direction: 'rtl', - 'box-sizing': 'border-box', - }, - }, - [ - createElement('span', { - style: { - display: 'block', - background: 'blue', - height: '100px', - left: 'auto', - position: 'absolute', - top: 'auto', - width: '100px', - 'box-sizing': 'border-box', - }, - }), - ] - )), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(); - }); - it('023', async () => { - let p; - let div3; - let div2; - let div1; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if a blue square is at the `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`bottom-left corner`)] - ), - createText(` of the page.`), - ] - ); - div1 = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - id: 'div1', - style: { - margin: '100px', - 'box-sizing': 'border-box', - }, - }, - [ - (div2 = createElement( - 'div', - { - id: 'div2', - style: { - margin: '100px', - 'box-sizing': 'border-box', - }, - }, - [ - (div3 = createElement('div', { - id: 'div3', - style: { - background: 'blue', - height: '100px', - left: '0', - position: 'absolute', - bottom: '0', - width: '100px', - 'box-sizing': 'border-box', - }, - })), - ] - )), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div1); - - await snapshot(); - }); - it('026', async () => { - let p; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Test passes if there is a filled green square and `), - createElement( - 'strong', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`no red`)] - ), - createText(`.`), - ] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - background: 'green', - height: '100px', - width: '100px', - 'box-sizing': 'border-box', - }, - }, - [ - createElement('div', { - style: { - background: 'green', - height: '100px', - width: '100px', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(); - }); - it('027', async () => { - let p; - let child; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText( - `Test passes if the orange rectangle is within or overflows to the right and outside of the blue square.` - ), - ] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - background: 'blue', - height: '300px', - 'padding-top': '5px', - width: '100px', - 'box-sizing': 'border-box', - }, - }, - [ - (child = createElement('div', { - id: 'child', - style: { - background: 'orange', - height: '100px', - 'padding-top': '5px', - width: '200px', - 'box-sizing': 'border-box', - }, - })), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(); - }); - it('028', async () => { - let p; - let child; - let div; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText( - `Test passes if a small orange square is in the bottom right corner of the blue square.` - ), - ] - ); - div = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: { - background: 'blue', - height: '100px', - position: 'absolute', - width: '100px', - 'box-sizing': 'border-box', - }, - }, - [ - (child = createElement('div', { - id: 'child', - style: { - background: 'orange', - height: '25px', - position: 'absolute', - width: '25px', - bottom: '0', - right: '0', - 'box-sizing': 'border-box', - }, - })), - ] - ); - BODY.appendChild(p); - BODY.appendChild(div); - - await snapshot(); - }); - it('030', async () => { - let p; - let soleChildWithTallerContent; - let containingBlock; - p = createElement( - 'p', - { - xmlns: 'http://www.w3.org/1999/xhtml', - style: {}, - }, - [ - createText( - `Test passes if the orange rectangle is within or overflows below and outside of the blue square.` - ), - ] - ); - containingBlock = createElement( - 'div', - { - xmlns: 'http://www.w3.org/1999/xhtml', - id: 'containing-block', - style: { - 'background-color': 'blue', - height: '100px', - 'padding-left': '5px', - width: '100px', - }, - }, - [ - (soleChildWithTallerContent = createElement('div', { - id: 'sole-child-with-taller-content', - style: { - 'background-color': 'orange', - height: '200px', - width: '50px', - }, - })), - ] - ); - BODY.appendChild(p); - BODY.appendChild(containingBlock); - - await snapshot(); - }); - it('percent-margin-bottom', async () => { - let p; - let container; - p = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`There should be a blue square below.`)] - ); - container = createElement( - 'div', - { - id: 'container', - 'data-expected-width': '100', - 'data-expected-height': '100', - style: { - overflow: 'hidden', - background: 'blue', - 'box-sizing': 'border-box', - width: '100px', - }, - }, - [ - createElement('div', { - style: { - 'margin-bottom': '50%', - height: '50px', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(container); - - await snapshot(); - }); - it('percent-margin-left', async () => { - let p; - let container; - p = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`There should be a blue square below.`)] - ); - container = createElement( - 'div', - { - id: 'container', - style: { - 'box-sizing': 'border-box', - width: '200px', - }, - }, - [ - createElement('div', { - 'data-expected-width': '100', - 'data-expected-height': '100', - style: { - 'margin-left': '50%', - height: '100px', - background: 'blue', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(container); - - await snapshot(); - }); - it('percent-margin-right', async () => { - let p; - let container; - p = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`There should be a blue square below.`)] - ); - container = createElement( - 'div', - { - id: 'container', - style: { - 'box-sizing': 'border-box', - width: '200px', - }, - }, - [ - createElement('div', { - 'data-expected-width': '100', - 'data-expected-height': '100', - style: { - 'margin-right': '50%', - height: '100px', - background: 'blue', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(container); - - await snapshot(); - }); - it('percent-margin-top', async () => { - let p; - let container; - p = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`There should be a blue square below.`)] - ); - container = createElement( - 'div', - { - id: 'container', - 'data-expected-width': '100', - 'data-expected-height': '100', - style: { - overflow: 'hidden', - background: 'blue', - 'box-sizing': 'border-box', - width: '100px', - }, - }, - [ - createElement('div', { - style: { - 'margin-top': '50%', - height: '50px', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(container); - - await snapshot(); - }); - it('percent-padding-bottom', async () => { - let p; - let container; - p = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`There should be a blue square below.`)] - ); - container = createElement( - 'div', - { - id: 'container', - style: { - 'box-sizing': 'border-box', - width: '500px', - }, - }, - [ - createElement('div', { - 'data-expected-width': '100', - 'data-expected-height': '100', - style: { - 'padding-bottom': '10%', - width: '100px', - height: '50px', - background: 'blue', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(container); - - await snapshot(); - }); - it('percent-padding-left', async () => { - let p; - let container; - p = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`There should be a blue square below.`)] - ); - container = createElement( - 'div', - { - id: 'container', - style: { - 'box-sizing': 'border-box', - width: '500px', - }, - }, - [ - createElement('div', { - 'data-expected-width': '100', - 'data-expected-height': '100', - style: { - 'padding-left': '10%', - width: '50px', - height: '100px', - background: 'blue', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(container); - - await snapshot(); - }); - it('percent-padding-right', async () => { - let p; - let container; - p = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`There should be a blue square below.`)] - ); - container = createElement( - 'div', - { - id: 'container', - style: { - 'box-sizing': 'border-box', - width: '500px', - }, - }, - [ - createElement('div', { - 'data-expected-width': '100', - 'data-expected-height': '100', - style: { - 'padding-right': '10%', - width: '50px', - height: '100px', - background: 'blue', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(container); - - await snapshot(); - }); - it('percent-padding-top', async () => { - let p; - let container; - p = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`There should be a blue square below.`)] - ); - container = createElement( - 'div', - { - id: 'container', - style: { - 'box-sizing': 'border-box', - width: '500px', - }, - }, - [ - createElement('div', { - 'data-expected-width': '100', - 'data-expected-height': '100', - style: { - 'padding-top': '10%', - width: '100px', - height: '50px', - background: 'blue', - 'box-sizing': 'border-box', - }, - }), - ] - ); - BODY.appendChild(p); - BODY.appendChild(container); - - await snapshot(); - }); -}); diff --git a/integration_tests/specs/dom/elements/custom-element.ts b/integration_tests/specs/dom/elements/custom-element.ts index 65fb007351..759b918e5c 100644 --- a/integration_tests/specs/dom/elements/custom-element.ts +++ b/integration_tests/specs/dom/elements/custom-element.ts @@ -37,6 +37,8 @@ describe('custom widget element', () => { done(); }); + await sleep(0.2); + simulateClick(20, 20); }); @@ -156,7 +158,7 @@ describe('custom widget element', () => { flutterContainer.style.display = 'block'; document.body.appendChild(flutterContainer); - + const div = document.createElement('div'); div.style.width = '100%'; div.style.height = '100px'; @@ -167,12 +169,12 @@ describe('custom widget element', () => { div.appendChild(img); flutterContainer.appendChild(div); - + requestAnimationFrame(async () => { const rect = div.getBoundingClientRect(); expect(rect.height).toEqual(100); done(); - }); + }); }); it('flutter widget should spread out the parent node when parent node is line-block', async () => { @@ -243,6 +245,12 @@ describe('custom html element', () => { await snapshot(); }); + it('dart implements getAllBindingPropertyNames works', async () => { + let sampleElement = document.createElement('sample-element'); + let attributes = Object.keys(sampleElement); + expect(attributes).toEqual(['ping', 'fake', 'fn', 'asyncFn', 'asyncFnFailed', 'asyncFnNotComplete']); + }); + it('support custom properties in dart directly', () => { let sampleElement = document.createElement('sample-element'); let text = document.createTextNode('helloworld'); @@ -261,7 +269,11 @@ describe('custom html element', () => { let arrs = [1, 2, 4, 8, 16]; // @ts-ignore - expect(sampleElement.fn.apply(sampleElement, arrs)).toEqual([2, 4, 8, 16, 32]); + let fn = sampleElement.fn; + + expect(fn.apply(sampleElement, arrs)).toEqual([2, 4, 8, 16, 32]); + // @ts-ignore + expect(fn.apply(sampleElement, arrs)).toEqual([2, 4, 8, 16, 32]); }); it('return promise when dart return future async function', async () => { @@ -287,6 +299,24 @@ describe('custom html element', () => { expect(await p4).toEqual([{ name: 1 }]); }); + it('return promise maybe not complete from dart side', async (done) => { + let sampleElement = document.createElement('sample-element'); + let text = document.createTextNode('helloworld'); + sampleElement.appendChild(text); + document.body.appendChild(sampleElement); + // @ts-ignore + let p = sampleElement.asyncFnNotComplete(); + expect(p instanceof Promise); + + p.then(() => { + done.fail('should not resolved'); + }); + + setTimeout(() => { + done(); + }, 2000); + }); + it('return promise error when dart async function throw error', async () => { let sampleElement = document.createElement('sample-element'); let text = document.createTextNode('helloworld'); @@ -299,7 +329,7 @@ describe('custom html element', () => { let result = await p; throw new Error('should throw'); } catch (e) { - expect(e.message).toBe('Assertion failed: "Asset error"'); + expect(e.message.trim()).toBe('Assertion failed: "Asset error"'); } }); @@ -310,11 +340,51 @@ describe('custom html element', () => { document.body.appendChild(sampleElement); // @ts-ignore - expect(sampleElement._fake).toBe(undefined); + expect(sampleElement._fake).toBe(null); // @ts-ignore sampleElement._fake = [1, 2, 3, 4, 5]; // @ts-ignore + sampleElement._fn = () => 1; + // @ts-ignore + sampleElement._self = sampleElement; + // @ts-ignore expect(sampleElement._fake).toEqual([1, 2, 3, 4, 5]); + // @ts-ignore + expect(sampleElement._fn()).toBe(1); + // @ts-ignore + expect(sampleElement._self === sampleElement); + }); + + it('should work with cloneNode', () => { + let sampleElement = document.createElement('sample-element'); + let text = document.createTextNode('helloworld'); + sampleElement.appendChild(text); + document.body.appendChild(sampleElement); + + // @ts-ignore + expect(sampleElement._fake).toBe(null); + + // @ts-ignore + sampleElement._fake = [1, 2, 3, 4, 5]; + // @ts-ignore + sampleElement._fn = () => 1; + // @ts-ignore + sampleElement._self = sampleElement; + // @ts-ignore + expect(sampleElement._fake).toEqual([1, 2, 3, 4, 5]); + // @ts-ignore + expect(sampleElement._fn()).toBe(1); + // @ts-ignore + expect(sampleElement._self === sampleElement); + + let clone = sampleElement.cloneNode(); + + // @ts-ignore + expect(clone._fake).toEqual([1,2,3,4,5]); + // @ts-ignore + expect(clone._fn()).toEqual(1); + // @ts-ignore + expect(clone._self).toBe(sampleElement); }); }); diff --git a/integration_tests/specs/dom/elements/img.ts b/integration_tests/specs/dom/elements/img.ts index 13d5ed46ea..5abcdef92d 100644 --- a/integration_tests/specs/dom/elements/img.ts +++ b/integration_tests/specs/dom/elements/img.ts @@ -407,9 +407,6 @@ describe('Tags img', () => { div.appendChild(document.createTextNode(i)); const img = document.createElement('img'); - img.src = images[i % images.length]; - div.appendChild(img); - img.style.width = '80px'; img.onload = async () => { loadedCount++; if (loadedCount == imgCount) { @@ -417,6 +414,9 @@ describe('Tags img', () => { done(); } }; + img.src = images[i % images.length]; + div.appendChild(img); + img.style.width = '80px'; flutterContainer.appendChild(div); } diff --git a/integration_tests/specs/dom/elements/textarea.ts b/integration_tests/specs/dom/elements/textarea.ts index 3c2e023968..22fd8736ba 100644 --- a/integration_tests/specs/dom/elements/textarea.ts +++ b/integration_tests/specs/dom/elements/textarea.ts @@ -581,7 +581,7 @@ describe('Tags textarea', () => { expect(textarea.value).toBe(''); }); - it('textarea attribute and property value priority', () => { + it('textarea attribute and property value priority', (done) => { let text; const textarea = createElement('textarea', { rows: 10, @@ -611,6 +611,8 @@ describe('Tags textarea', () => { text.data = 'text content value 2'; expect(textarea.defaultValue).toBe('text content value 2'); expect(textarea.value).toBe('property value'); + + done() }); }); }); diff --git a/integration_tests/specs/dom/events/event.ts b/integration_tests/specs/dom/events/event.ts index f8a5abd021..168244937d 100644 --- a/integration_tests/specs/dom/events/event.ts +++ b/integration_tests/specs/dom/events/event.ts @@ -471,4 +471,23 @@ describe('Event', () => { expect(ret).toEqual('1'); }); + it('should work with undefined addEventListener options', () => { + var el = createElement('div', { + style: { + width: '100px', + height: '100px', + background: 'red' + } + }); + let ret = ''; + function fn1() { + ret += '1'; + } + el.addEventListener('click', fn1, undefined); + el.addEventListener('scroll', fn1, undefined); + + el.click(); + + expect(ret).toEqual('1'); + }); }); diff --git a/integration_tests/specs/dom/nodes/append-child.tsx b/integration_tests/specs/dom/nodes/append-child.tsx index 324d1fdf5e..52f2a1af77 100644 --- a/integration_tests/specs/dom/nodes/append-child.tsx +++ b/integration_tests/specs/dom/nodes/append-child.tsx @@ -4,11 +4,11 @@ describe('Append child', () => { expect(() => { // @ts-ignore container.appendChild({name: 1}); - }).toThrowError('Failed to execute \'appendChild\' on \'Node\': first arguments should be an Node type.'); + }).toThrowError('parameter 1 is not of type \'Node\'.'); expect(() => { // @ts-ignore container.appendChild(new Event('1234')); - }).toThrowError('Failed to execute \'appendChild\' on \'Node\': first arguments should be an Node type.'); + }).toThrowError('parameter 1 is not of type \'Node\'.'); }); it('with orphan element', async () => { const style = { diff --git a/integration_tests/specs/dom/nodes/clone-node.ts b/integration_tests/specs/dom/nodes/clone-node.ts index e56958e846..c6aef9e14e 100644 --- a/integration_tests/specs/dom/nodes/clone-node.ts +++ b/integration_tests/specs/dom/nodes/clone-node.ts @@ -108,13 +108,14 @@ describe('Clone node', () => { it('should work with img element', async (done) => { const img = document.createElement('img'); + img.addEventListener('load', loadImg); img.style.width = '100px'; img.style.height = '100px'; img.src = "assets/kraken.png"; document.body.appendChild(img); const img2 = img.cloneNode(false); + img2.addEventListener('load', loadImg); document.body.appendChild(img2); - let anotherImgHasLoad = false; async function loadImg() { if (anotherImgHasLoad) { @@ -124,9 +125,6 @@ describe('Clone node', () => { anotherImgHasLoad = true; } } - - img.addEventListener('load', loadImg); - img2.addEventListener('load', loadImg); }) it('deep is not required', async () => { diff --git a/integration_tests/specs/dom/nodes/get-element-by-id.ts b/integration_tests/specs/dom/nodes/get-element-by-id.ts index 243c0458af..d59b98a7eb 100644 --- a/integration_tests/specs/dom/nodes/get-element-by-id.ts +++ b/integration_tests/specs/dom/nodes/get-element-by-id.ts @@ -5,14 +5,14 @@ describe('Document getElementById', () => { it('basic test', () => { const div = document.createElement('div'); - div.setAttribute('id', 'div'); + div.id = 'div'; const myDiv = document.getElementById('div'); expect(myDiv).toBeNull(); }); it('not work with element not inserted into Document', () => { const div = document.createElement('div'); - div.setAttribute('id', 'div'); + div.id = 'div'; expect(document.getElementById('div')).toBeNull(); }); diff --git a/integration_tests/specs/dom/nodes/insert-before.ts b/integration_tests/specs/dom/nodes/insert-before.ts index ba06cc5c2c..8412ceca1b 100644 --- a/integration_tests/specs/dom/nodes/insert-before.ts +++ b/integration_tests/specs/dom/nodes/insert-before.ts @@ -7,7 +7,7 @@ describe('Insert before', () => { expect(() => { // @ts-ignore container.insertBefore(new Event('1234'), null); - }).toThrowError('Failed to execute \'insertBefore\' on \'Node\': parameter 1 is not of type \'Node\''); + }).toThrowError('parameter 1 is not of type \'Node\'.'); }); it('with node is a child of another parent', () => { let container = document.createElement('div'); diff --git a/integration_tests/specs/dom/nodes/replace-child.ts b/integration_tests/specs/dom/nodes/replace-child.ts index 6ab9023a94..f3ad070908 100644 --- a/integration_tests/specs/dom/nodes/replace-child.ts +++ b/integration_tests/specs/dom/nodes/replace-child.ts @@ -5,7 +5,7 @@ describe('Replace child', () => { let newChild = document.createElement('div'); expect(() => { container.replaceChild(newChild, node); - }).toThrowError('Failed to execute \'replaceChild\' on \'Node\': The node to be replaced is not a child of this node.'); + }).toThrowError('The node to be replaced is not a child of this node.'); }); it('with old child is not an type of node', () => { let container = document.createElement('div'); diff --git a/integration_tests/specs/dom/nodes/textNode.ts b/integration_tests/specs/dom/nodes/textNode.ts index dbcb1d2058..ea1dcd8b89 100644 --- a/integration_tests/specs/dom/nodes/textNode.ts +++ b/integration_tests/specs/dom/nodes/textNode.ts @@ -175,7 +175,7 @@ describe('TextNode', () => { it('should work with whitespace trim and collapse of space', async () => { let div; - + div = createElement( 'div', { @@ -185,7 +185,7 @@ describe('TextNode', () => { }, [createText(`\u0020 \u0020A\u0020 \u0020B`)] ); - + BODY.appendChild(div); await snapshot(); @@ -193,7 +193,7 @@ describe('TextNode', () => { it('should work with whitespace trim and collapse of tab', async () => { let div; - + div = createElement( 'div', { @@ -203,7 +203,7 @@ describe('TextNode', () => { }, [createText(`\u0009\u0009\u0009A\u0009\u0009\u0009B`)] ); - + BODY.appendChild(div); await snapshot(); @@ -211,7 +211,7 @@ describe('TextNode', () => { it('should work with whitespace trim and collapse of segment break', async () => { let div; - + div = createElement( 'div', { @@ -221,7 +221,7 @@ describe('TextNode', () => { }, [createText(`\u000a\u000a\u000aA\u000a\u000a\u000aB`)] ); - + BODY.appendChild(div); await snapshot(); @@ -229,7 +229,7 @@ describe('TextNode', () => { it('should work with whitespace trim and collapse of carriage return', async () => { let div; - + div = createElement( 'div', { @@ -239,7 +239,7 @@ describe('TextNode', () => { }, [createText(`\u000d\u000d\u000dA\u000d\u000d\u000dB`)] ); - + BODY.appendChild(div); await snapshot(); @@ -247,7 +247,7 @@ describe('TextNode', () => { it('should not work with whitespace trim and collapse of no-break space', async () => { let div; - + div = createElement( 'div', { @@ -257,7 +257,7 @@ describe('TextNode', () => { }, [createText(`\u00a0\u00a0\u00a0A\u00a0\u00a0\u00a0B`)] ); - + BODY.appendChild(div); await snapshot(); diff --git a/integration_tests/specs/method-channel/method-channel.ts b/integration_tests/specs/method-channel/method-channel.ts deleted file mode 100644 index f8c39b24b4..0000000000 --- a/integration_tests/specs/method-channel/method-channel.ts +++ /dev/null @@ -1,62 +0,0 @@ -describe('MethodChannel', () => { - it('addMethodCallHandler multi params', async (done) => { - webf.methodChannel.addMethodCallHandler((method: string, args: any[]) => { - expect(method).toBe('helloworld'); - expect(args).toEqual(['abc', 1234, null, /* undefined will be converted to */ null, [], true, false, {name: 1}]); - done(); - }); - let result = await webf.methodChannel.invokeMethod('helloworld', 'abc', 1234, null, undefined, [], true, false, {name: 1}); - expect(result).toBe('method: helloworld'); - }); - - it('invokeMethod', async () => { - let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); - // TEST App will return method string - expect(result).toBe('method: helloworld'); - }); - - it('addMethodCallHandler', async (done) => { - webf.methodChannel.addMethodCallHandler((method: string, args: any[]) => { - expect(method).toBe('helloworld'); - expect(args).toEqual(['abc']); - done(); - }); - let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); - expect(result).toBe('method: helloworld'); - }); - - - it('removeMethodCallHandler', async (done: DoneFn) => { - var handler = (method: string, args: any[]) => { - done.fail('should not execute here.'); - }; - webf.methodChannel.addMethodCallHandler(handler); - webf.methodChannel.removeMethodCallHandler(handler); - let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); - expect(result).toBe('method: helloworld'); - done(); - }); - - it('addMethodCallHandler multi params with multi handler', async (done) => { - let handlerCount = 0; - webf.methodChannel.addMethodCallHandler((method: string, args: any[]) => { - handlerCount++; - expect(method).toBe('helloworld'); - expect(args).toEqual(['abc', 1234, null, /* undefined will be converted to */ null, [], true, false, {name: 1}]); - if(handlerCount == 2) { - done(); - } - }); - webf.methodChannel.addMethodCallHandler((method: string, args: any[]) => { - handlerCount++; - expect(method).toBe('helloworld'); - expect(args).toEqual(['abc', 1234, null, /* undefined will be converted to */ null, [], true, false, {name: 1}]); - if (handlerCount == 2) { - done(); - } - }); - let result = await webf.methodChannel.invokeMethod('helloworld', 'abc', 1234, null, undefined, [], true, false, {name: 1}); - expect(result).toBe('method: helloworld'); - expect(handlerCount).toBe(2); - }); -}); diff --git a/integration_tests/specs/modules/method-channel.ts b/integration_tests/specs/modules/method-channel.ts new file mode 100644 index 0000000000..83ea5d1471 --- /dev/null +++ b/integration_tests/specs/modules/method-channel.ts @@ -0,0 +1,46 @@ +describe('MethodChannel', () => { + it('addMethodCallHandler multi params', async () => { + webf.methodChannel.addMethodCallHandler('helloworld', (args: any) => { + expect(args).toEqual(['abc', 1234, null, /* undefined will be converted to */ null, [], true, false, {name: 1}]); + return 'from helloworld' + args[0]; + }); + let result = await webf.methodChannel.invokeMethod('helloworld', 'abc', 1234, null, undefined, [], true, false, {name: 1}); + expect(result).toBe('method: helloworld, return_type: String, return_value: from helloworldabc'); + }); + + it('invokeMethod', async () => { + let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); + // TEST App will return method string + expect(result).toBe('method: helloworld, return_type: Null, return_value: null'); + }); + + it('addMethodCallHandler', async () => { + webf.methodChannel.addMethodCallHandler('helloworld', (args: any[]) => { + expect(args).toEqual(['abc']); + return 0; + }); + let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); + expect(result).toBe('method: helloworld, return_type: int, return_value: 0'); + }); + + it('addMethodCallHandler can return value', async () => { + webf.methodChannel.addMethodCallHandler('helloworld', (args: any[]) => { + expect(args).toEqual(['abc']); + return true; + }); + let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); + expect(result).toBe('method: helloworld, return_type: bool, return_value: true'); + }); + + + it('removeMethodCallHandler', async (done: DoneFn) => { + var handler = (args: any[]) => { + done.fail('should not execute here.'); + }; + webf.methodChannel.addMethodCallHandler('helloworld', handler); + webf.methodChannel.removeMethodCallHandler('helloworld'); + let result = await webf.methodChannel.invokeMethod('helloworld', 'abc'); + expect(result).toBe('method: helloworld, return_type: Null, return_value: null'); + done(); + }); +}); diff --git a/integration_tests/specs/modules/raw_module.ts b/integration_tests/specs/modules/raw_module.ts new file mode 100644 index 0000000000..916c674003 --- /dev/null +++ b/integration_tests/specs/modules/raw_module.ts @@ -0,0 +1,65 @@ +describe('Modules.invokeModule', () => { + it('invokeModule can have no params', () => { + let result = webf.invokeModule('Demo', 'noParams'); + expect(result).toBe(true); + }); + it('invokeModule can accept int', () => { + let result = webf.invokeModule('Demo', 'callInt', 10); + expect(result).toBe(20); + }); + it('invokeModule can accept double', () => { + let result = webf.invokeModule('Demo', 'callDouble', 14.5); + expect(result).toBe(29.0); + }); + it('invokeModule can accept string', () => { + let result = webf.invokeModule('Demo', 'callString', 'helloworld'); + expect(result).toBe('HELLOWORLD'); + }); + it('invokeModule can accept array', () => { + let result = webf.invokeModule('Demo', 'callArray', [1, 2, 3, 4, 5]); + expect(result).toBe(15); + }); + it('invokeModule can accept null or undefined', () => { + expect(webf.invokeModule('Demo', 'callNull', null)).toBe(null); + expect(webf.invokeModule('Demo', 'callNull', undefined)).toBe(null); + }); + it('invokeModule can accept callback and receive value from callback', () => { + return new Promise((resolve, reject) => { + let callParams = 10; + let syncResult = webf.invokeModule('Demo', 'callAsyncFn', callParams, (err, data) => { + if (err) { + return reject(err); + } + expect(data).toEqual([1, '2', null, 4.0, { value: 1}]); + setTimeout(() => resolve()); + return 'success'; + }); + expect(syncResult).toBe(callParams); + }); + }); + it('invokeModule can accept callback and handle the error', () => { + return new Promise((resolve) => { + let syncResult = webf.invokeModule('Demo', 'callAsyncFnFail', null, (err, data) => { + expect(err).toBeInstanceOf(Error); + expect(err.message).toBe('Must to fail'); + setTimeout(() => resolve()); + return 'fail'; + }); + expect(syncResult).toBe(null); + }); + }); +}); + +describe('Module.addModuleListener', () => { + it('event params should works', (done) => { + webf.addWebfModuleListener('Demo', (event: CustomEvent, extra) => { + expect(event instanceof CustomEvent).toBe(true); + expect(event.type).toBe('click'); + expect(event.detail).toBe('helloworld'); + expect(extra).toEqual([1,2,3,4,5]); + setTimeout(() => done()); + return 'success'; + }); + webf.invokeModule('Demo', 'callToDispatchEvent'); + }); +}); \ No newline at end of file diff --git a/scripts/tasks.js b/scripts/tasks.js index 54b9b1f91f..fb16aba75f 100644 --- a/scripts/tasks.js +++ b/scripts/tasks.js @@ -119,7 +119,8 @@ task('build-darwin-webf-lib', done => { webfTargets.push('webf_test'); } - execSync(`cmake --build ${paths.bridge}/cmake-build-macos-x86_64 --target ${webfTargets.join(' ')} -- -j 6`, { + let cpus = os.cpus(); + execSync(`cmake --build ${paths.bridge}/cmake-build-macos-x86_64 --target ${webfTargets.join(' ')} -- -j ${cpus.length}`, { stdio: 'inherit' }); diff --git a/webf/CHANGELOG.md b/webf/CHANGELOG.md index 3743236dfe..d316669a4b 100644 --- a/webf/CHANGELOG.md +++ b/webf/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.13.0-beta.3 + +* Fix reload crash. + +## 0.13.0-beta.2 + +* Test for new bridge and css selector. + ## 0.12.0+2 **Bug Fixed** diff --git a/webf/android/build.gradle b/webf/android/build.gradle index 267e0ae98e..98b660c5e4 100644 --- a/webf/android/build.gradle +++ b/webf/android/build.gradle @@ -1,4 +1,4 @@ -group 'com.openwebf.kraken' +group 'com.openwebf.webf' version '1.0' buildscript { diff --git a/webf/android/src/main/java/com/openwebf/webf/WebF.java b/webf/android/src/main/java/com/openwebf/webf/WebF.java index a1768f08af..addc05fe04 100644 --- a/webf/android/src/main/java/com/openwebf/webf/WebF.java +++ b/webf/android/src/main/java/com/openwebf/webf/WebF.java @@ -12,8 +12,6 @@ import io.flutter.plugin.common.MethodChannel; public class WebF { - - private String url; private String dynamicLibraryPath; private FlutterEngine flutterEngine; @@ -36,13 +34,6 @@ public static WebF get(FlutterEngine engine) { public void registerMethodCallHandler(MethodChannel.MethodCallHandler handler) { this.handler = handler; } - /** - * Load url. - * @param url - */ - public void loadUrl(String url) { - this.url = url; - } /** * Set the dynamic library path. @@ -51,48 +42,10 @@ public void loadUrl(String url) { public void setDynamicLibraryPath(String value) { this.dynamicLibraryPath = value; } - - public String getUrl() { - return url; - } - public String getDynamicLibraryPath() { return dynamicLibraryPath != null ? dynamicLibraryPath : ""; } - public void _handleMethodCall(MethodCall call, MethodChannel.Result result) { - if (this.handler != null) { - this.handler.onMethodCall(call, result); - } else { - result.error("No handler found.", null, null); - } - } - - public void invokeMethod(final String method, final Object arguments) { - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - if (flutterEngine != null) { - PluginRegistry pluginRegistry = flutterEngine.getPlugins(); - WebFPlugin webFPlugin = (WebFPlugin) pluginRegistry.get(WebFPlugin.class); - if (webFPlugin != null && webFPlugin.channel != null) { - webFPlugin.channel.invokeMethod(method, arguments); - } - } - } - }); - } - - public void reload() { - if (flutterEngine != null) { - PluginRegistry pluginRegistry = flutterEngine.getPlugins(); - WebFPlugin webFPlugin = (WebFPlugin) pluginRegistry.get(WebFPlugin.class); - if (webFPlugin != null) { - webFPlugin.reload(); - } - } - } - public void destroy() { sdkMap.remove(flutterEngine); flutterEngine = null; diff --git a/webf/android/src/main/java/com/openwebf/webf/WebFPlugin.java b/webf/android/src/main/java/com/openwebf/webf/WebFPlugin.java index 0e4ebc7225..3395693682 100644 --- a/webf/android/src/main/java/com/openwebf/webf/WebFPlugin.java +++ b/webf/android/src/main/java/com/openwebf/webf/WebFPlugin.java @@ -47,12 +47,6 @@ public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) { channel.setMethodCallHandler(this); } - public void reload() { - if (channel != null) { - channel.invokeMethod("reload", null); - } - } - WebF getWebF() { if (mWebF == null) { mWebF = WebF.get(flutterEngine); @@ -63,36 +57,14 @@ WebF getWebF() { @Override public void onMethodCall(MethodCall call, Result result) { switch (call.method) { - case "getUrl": { - WebF webf = getWebF(); - result.success(webf == null ? "" : webf.getUrl()); - break; - } - case "getDynamicLibraryPath": { WebF webf = getWebF(); result.success(webf == null ? "" : webf.getDynamicLibraryPath()); break; } - - case "invokeMethod": { - WebF webf = getWebF(); - if (webf != null) { - String method = call.argument("method"); - Object args = call.argument("args"); - assert method != null; - MethodCall callWrap = new MethodCall(method, args); - webf._handleMethodCall(callWrap, result); - } else { - result.error("WebF instance not found.", null, null); - } - break; - } - case "getTemporaryDirectory": result.success(getTemporaryDirectory()); break; - default: result.notImplemented(); } diff --git a/webf/example/android/app/src/main/AndroidManifest.xml b/webf/example/android/app/src/main/AndroidManifest.xml index 77093c1f4f..0bae586864 100644 --- a/webf/example/android/app/src/main/AndroidManifest.xml +++ b/webf/example/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> + -#import "WebF.h" -#import "WebFPlugin.h" - -typedef void(^MethodHandler)(FlutterMethodCall* _Nonnull , FlutterResult _Nonnull); - -@interface WebF : NSObject - -+ (WebF* _Nonnull) instanceByBinaryMessenger: (NSObject* _Nonnull) messenger; - -@property NSString* _Nullable bundleUrl; -@property FlutterEngine* _Nonnull flutterEngine; -@property FlutterMethodChannel* _Nullable channel; -@property MethodHandler _Nullable methodHandler; - -- (instancetype _Nonnull)initWithFlutterEngine: (FlutterEngine* _Nonnull) engine; - -- (NSString* _Nullable) getUrl; - -- (void) loadUrl: (NSString* _Nonnull)url; - -- (void) reload; - -- (void) reloadWithUrl: (NSString* _Nonnull) url; - -- (void) registerMethodCallHandler: (MethodHandler _Nonnull) handler; - -- (void) invokeMethod: (NSString* _Nonnull)method arguments:(nullable id) arguments; - -- (void) _handleMethodCall:(FlutterMethodCall* _Nonnull)call result:(FlutterResult _Nonnull )result; -@end diff --git a/webf/ios/Classes/WebF.m b/webf/ios/Classes/WebF.m deleted file mode 100644 index 2e66696ad8..0000000000 --- a/webf/ios/Classes/WebF.m +++ /dev/null @@ -1,89 +0,0 @@ -#import -#import "WebF.h" - -static NSMutableArray *engineList = nil; -static NSMutableArray *instanceList = nil; - -@implementation WebF - -+ (WebF*) instanceByBinaryMessenger: (NSObject*) messenger { - for (int i = 0; i < engineList.count; i++) { - FlutterEngine *engine = engineList[i]; - if (engine != nil && engine.viewController != nil && engine.viewController.binaryMessenger != nil) { - if (engine.viewController.binaryMessenger == messenger) { - return [instanceList objectAtIndex:i]; - } - } - } - return nil; -} - -- (instancetype)initWithFlutterEngine: (FlutterEngine*) engine { - self.flutterEngine = engine; - - FlutterMethodChannel *channel = [WebFPlugin getMethodChannel]; - - if (channel == nil) { - NSException* exception = [NSException - exceptionWithName:@"InitError" - reason:@"WebFSDK should init after Flutter's plugin registered." - userInfo:nil]; - @throw exception; - } - self.channel = channel; - - if (engineList == nil) { - engineList = [[NSMutableArray alloc] initWithCapacity: 0]; - } - [engineList addObject: engine]; - - if (instanceList == nil) { - instanceList = [[NSMutableArray alloc] initWithCapacity: 0]; - } - [instanceList addObject: self]; - - return self; -} - -- (void) loadUrl:(NSString*)url { - if (url != nil) { - self.bundleUrl = url; - } -} - -- (void) reload { - if (self.channel != nil) { - [self.channel invokeMethod:@"reload" arguments:nil]; - } -} - -- (void) reloadWithUrl: (NSString*) url { - [self loadUrl: url]; - [self reload]; -} - -- (NSString*) getUrl { - return self.bundleUrl; -} - -- (void) invokeMethod:(NSString *)method arguments:(nullable id) arguments { - dispatch_async(dispatch_get_main_queue(), ^{ - if (self.channel != nil) { - [self.channel invokeMethod:method arguments:arguments]; - } - }); -} - -- (void) _handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - if (self.methodHandler != nil) { - self.methodHandler(call, result); - } -} - -- (void) registerMethodCallHandler: (MethodHandler) handler { - self.methodHandler = handler; -} - -@end - - diff --git a/webf/ios/Classes/WebFPlugin.h b/webf/ios/Classes/WebFPlugin.h index eff58de613..1b7536c97c 100644 --- a/webf/ios/Classes/WebFPlugin.h +++ b/webf/ios/Classes/WebFPlugin.h @@ -1,7 +1,5 @@ #import -#define NAME_METHOD_SPLIT @"!!" - @interface WebFPlugin : NSObject @property NSObject *registrar; diff --git a/webf/ios/Classes/WebFPlugin.m b/webf/ios/Classes/WebFPlugin.m index 72fa8b8e05..013537be31 100644 --- a/webf/ios/Classes/WebFPlugin.m +++ b/webf/ios/Classes/WebFPlugin.m @@ -1,4 +1,3 @@ -#import "WebF.h" #import "WebFPlugin.h" static FlutterMethodChannel *methodChannel = nil; @@ -29,18 +28,7 @@ - (instancetype) initWithRegistrar: (NSObject*)registrar } - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - if ([@"getUrl" isEqualToString:call.method]) { - WebF* webfInstance = [WebF instanceByBinaryMessenger: [self.registrar messenger]]; - if (webfInstance != nil) { - result([webfInstance getUrl]); - } else { - result(nil); - } - } else if ([@"invokeMethod" isEqualToString: call.method]) { - WebF* webfInstance = [WebF instanceByBinaryMessenger: [self.registrar messenger]]; - FlutterMethodCall* callWrap = [FlutterMethodCall methodCallWithMethodName: call.arguments[@"method"] arguments: call.arguments[@"args"]]; - [webfInstance _handleMethodCall:callWrap result:result]; - } else if ([@"getTemporaryDirectory" isEqualToString: call.method]) { + if ([@"getTemporaryDirectory" isEqualToString: call.method]) { result([self getTemporaryDirectory]); } else { result(FlutterMethodNotImplemented); diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index f0ae57fbb5..98893a9ffc 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -15,94 +15,190 @@ import 'package:webf/dom.dart'; import 'package:webf/foundation.dart'; // We have some integrated built-in behavior starting with string prefix reuse the callNativeMethod implements. -const String AnonymousFunctionCallPreFix = '_anonymous_fn_'; -const String AsyncAnonymousFunctionCallPreFix = '_anonymous_async_fn_'; -const String GetPropertyMagic = '%g'; -const String SetPropertyMagic = '%s'; +enum BindingMethodCallOperations { + GetProperty, + SetProperty, + GetAllPropertyNames, + AnonymousFunctionCall, + AsyncAnonymousFunction, +} typedef NativeAsyncAnonymousFunctionCallback = Void Function( Pointer callbackContext, Pointer nativeValue, Int32 contextId, Pointer errmsg); typedef DartAsyncAnonymousFunctionCallback = void Function( Pointer callbackContext, Pointer nativeValue, int contextId, Pointer errmsg); +typedef BindingCallFunc = dynamic Function(BindingObject bindingObject, List args); + +dynamic getterBindingCall(BindingObject bindingObject, List args) { + assert(args.length == 1); + if (isEnabledLog) { + print('$bindingObject getBindingProperty key: ${args[0]} result: ${bindingObject.getBindingProperty(args[0])}'); + } + + return bindingObject.getBindingProperty(args[0]); +} + +dynamic setterBindingCall(BindingObject bindingObject, List args) { + assert(args.length == 2); + if (isEnabledLog) { + print('$bindingObject setBindingProperty key: ${args[0]} value: ${args[1]}'); + } + + bindingObject.setBindingProperty(args[0], args[1]); + return true; +} + +dynamic getPropertyNamesBindingCall(BindingObject bindingObject, List args) { + List properties = List.empty(growable: true); + bindingObject.getAllBindingPropertyNames(properties); + + if (isEnabledLog) { + print('$bindingObject getPropertyNamesBindingCall value: $properties'); + } + + return properties; +} + +List bindingCallMethodDispatchTable = [ + getterBindingCall, + setterBindingCall, + getPropertyNamesBindingCall, +]; + // This function receive calling from binding side. -void _invokeBindingMethod(Pointer nativeBindingObject, Pointer returnValue, - Pointer nativeMethod, int argc, Pointer argv) { - String method = nativeStringToString(nativeMethod); +void _invokeBindingMethodFromNativeImpl(Pointer nativeBindingObject, + Pointer returnValue, Pointer nativeMethod, int argc, Pointer argv) { + dynamic method = fromNativeValue(nativeMethod); List values = List.generate(argc, (i) { Pointer nativeValue = argv.elementAt(i); return fromNativeValue(nativeValue); }); - if (method.startsWith(AnonymousFunctionCallPreFix)) { - int id = int.parse(method.substring(AnonymousFunctionCallPreFix.length)); - AnonymousNativeFunction fn = getAnonymousNativeFunctionFromId(id)!; - try { - var result = fn(values); - toNativeValue(returnValue, result); - } catch (e, stack) { - print('$e\n$stack'); - toNativeValue(returnValue, null); - } - removeAnonymousNativeFunctionFromId(id); - } else if (method.startsWith(AsyncAnonymousFunctionCallPreFix)) { - int id = int.parse(method.substring(AsyncAnonymousFunctionCallPreFix.length)); - AsyncAnonymousNativeFunction fn = getAsyncAnonymousNativeFunctionFromId(id)!; - int contextId = values[0]; - Pointer callbackContext = (values[1] as Pointer).cast(); - DartAsyncAnonymousFunctionCallback callback = - (values[2] as Pointer).cast>().asFunction(); - Future p = fn(values.sublist(3)); - p.then((result) { - Pointer nativeValue = malloc.allocate(sizeOf()); - toNativeValue(nativeValue, result); - callback(callbackContext, nativeValue, contextId, nullptr); - removeAsyncAnonymousNativeFunctionFromId(id); - }).catchError((e, stack) { - String errorMessage = '$e'; - callback(callbackContext, nullptr, contextId, errorMessage.toNativeUtf8()); - removeAsyncAnonymousNativeFunctionFromId(id); - }); - - toNativeValue(returnValue, null); - } else { - // @TODO: Should not share the same binding method, and separate by magic. - BindingObject bindingObject = BindingBridge.getBindingObject(nativeBindingObject.cast()); - var result; - try { - if (method == GetPropertyMagic && argc == 1) { - result = bindingObject.getBindingProperty(values[0]); - } else if (method == SetPropertyMagic && argc == 2) { - bindingObject.setBindingProperty(values[0], values[1]); - result = null; + BindingObject bindingObject = BindingBridge.getBindingObject(nativeBindingObject); + var result = null; + try { + // Method is binding call method operations from internal. + if (method is int) { + // Get and setter ops + if (method <= 2) { + result = bindingCallMethodDispatchTable[method](bindingObject, values); } else { - result = bindingObject.invokeBindingMethod(method, values); + if (method == BindingMethodCallOperations.AnonymousFunctionCall.index) { + int id = values[0]; + List functionArguments = values.sublist(1); + AnonymousNativeFunction? fn = bindingObject.getAnonymousNativeFunctionFromId(id); + if (fn == null) { + print('WebF warning: can not find registered anonymous native function for id: $id bindingObject: $nativeBindingObject'); + toNativeValue(returnValue, null, bindingObject); + return; + } + try { + if (isEnabledLog) { + String argsStr = functionArguments.map((e) => e.toString()).join(','); + print('Invoke AnonymousFunction id: $id, arguments: [$argsStr] bindingObject: $nativeBindingObject'); + } + + result = fn(functionArguments); + } catch (e, stack) { + print('$e\n$stack'); + } + } else if (method == BindingMethodCallOperations.AsyncAnonymousFunction.index) { + int id = values[0]; + AsyncAnonymousNativeFunction? fn = bindingObject.getAsyncAnonymousNativeFunctionFromId(id); + if (fn == null) { + print('WebF warning: can not find registered anonymous native async function for id: $id bindingObject: $nativeBindingObject'); + toNativeValue(returnValue, null, bindingObject); + return; + } + int contextId = values[1]; + // Async callback should hold a context to store the current execution environment. + Pointer callbackContext = (values[2] as Pointer).cast(); + DartAsyncAnonymousFunctionCallback callback = + (values[3] as Pointer).cast>().asFunction(); + List functionArguments = values.sublist(4); + if (isEnabledLog) { + String argsStr = functionArguments.map((e) => e.toString()).join(','); + print('Invoke AsyncAnonymousFunction id: $id arguments: [$argsStr]'); + } + + Future p = fn(functionArguments); + p.then((result) { + if (isEnabledLog) { + print('AsyncAnonymousFunction call resolved callback: $id arguments:[$result]'); + } + Pointer nativeValue = malloc.allocate(sizeOf()); + toNativeValue(nativeValue, result, bindingObject); + callback(callbackContext, nativeValue, contextId, nullptr); + }).catchError((e, stack) { + String errorMessage = '$e\n$stack'; + if (isEnabledLog) { + print('AsyncAnonymousFunction call rejected callback: $id, arguments:[$errorMessage]'); + } + callback(callbackContext, nullptr, contextId, errorMessage.toNativeUtf8()); + }); + } } - } catch (e, stack) { - print('$e\n$stack'); - } finally { - toNativeValue(returnValue, result); + } else { + BindingObject bindingObject = BindingBridge.getBindingObject(nativeBindingObject); + // invokeBindingMethod directly + if (isEnabledLog) { + print('$bindingObject invokeBindingMethod method: $method args: $values'); + } + result = bindingObject.invokeBindingMethod(method, values); } + } catch (e, stack) { + print('$e\n$stack'); + } finally { + toNativeValue(returnValue, result, bindingObject); } } // Dispatch the event to the binding side. -void _dispatchBindingEvent(Event event) { +void _dispatchEventToNative(Event event) { Pointer? pointer = event.currentTarget?.pointer; int? contextId = event.target?.contextId; - if (contextId != null && pointer != null) { - emitUIEvent(contextId, pointer, event); + if (contextId != null && pointer != null && pointer.ref.invokeBindingMethodFromDart != nullptr) { + BindingObject bindingObject = BindingBridge.getBindingObject(pointer); + // Call methods implements at C++ side. + DartInvokeBindingMethodsFromDart f = pointer.ref.invokeBindingMethodFromDart.asFunction(); + + Pointer rawEvent = event.toRaw().cast(); + List dispatchEventArguments = [event.type, rawEvent]; + + if (isEnabledLog) { + print('dispatch event to native side: target: ${event.target} arguments: $dispatchEventArguments'); + } + + Pointer method = malloc.allocate(sizeOf()); + toNativeValue(method, 'dispatchEvent'); + Pointer allocatedNativeArguments = makeNativeValueArguments(bindingObject, dispatchEventArguments); + + Pointer returnValue = malloc.allocate(sizeOf()); + f(pointer, returnValue, method, dispatchEventArguments.length, allocatedNativeArguments); + Pointer dispatchResult = fromNativeValue(returnValue).cast(); + event.cancelable = dispatchResult.ref.canceled; + event.propagationStopped = dispatchResult.ref.propagationStopped; + + // Free the allocated arguments. + malloc.free(rawEvent); + malloc.free(method); + malloc.free(allocatedNativeArguments); + malloc.free(dispatchResult); + malloc.free(returnValue); } } abstract class BindingBridge { - static final Pointer> _nativeInvokeBindingMethod = - Pointer.fromFunction(_invokeBindingMethod); - static Pointer> get nativeInvokeBindingMethod => _nativeInvokeBindingMethod; + static final Pointer> _invokeBindingMethodFromNative = + Pointer.fromFunction(_invokeBindingMethodFromNativeImpl); + + static Pointer> get nativeInvokeBindingMethod => + _invokeBindingMethodFromNative; static final SplayTreeMap _nativeObjects = SplayTreeMap(); - static BindingObject getBindingObject(Pointer pointer) { + static BindingObject getBindingObject(Pointer pointer) { BindingObject? target = _nativeObjects[pointer.address]; if (target == null) { throw FlutterError('Can not get binding object: $pointer'); @@ -111,28 +207,18 @@ abstract class BindingBridge { } static void _bindObject(BindingObject object) { - Pointer? nativeBindingObject = castToType(object.pointer); - if (nativeBindingObject != null) { + Pointer? nativeBindingObject = object.pointer; + if (nativeBindingObject != null && !nativeBindingObject.ref.disposed) { _nativeObjects[nativeBindingObject.address] = object; - if (nativeBindingObject is Pointer) { - nativeBindingObject.ref.invokeBindingMethod = _nativeInvokeBindingMethod; - } else if (nativeBindingObject is Pointer) { - // @TODO: Remove it. - nativeBindingObject.ref.invokeBindingMethod = _nativeInvokeBindingMethod; - } + nativeBindingObject.ref.invokeBindingMethodFromNative = _invokeBindingMethodFromNative; } } static void _unbindObject(BindingObject object) { - Pointer? nativeBindingObject = castToType(object.pointer); + Pointer? nativeBindingObject = object.pointer; if (nativeBindingObject != null) { _nativeObjects.remove(nativeBindingObject.address); - if (nativeBindingObject is Pointer) { - nativeBindingObject.ref.invokeBindingMethod = nullptr; - } else if (nativeBindingObject is Pointer) { - // @TODO: Remove it. - nativeBindingObject.ref.invokeBindingMethod = nullptr; - } + nativeBindingObject.ref.invokeBindingMethodFromNative = nullptr; } } @@ -149,20 +235,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> eventHandlers = target.getEventHandlers(); List? handlers = eventHandlers[type]; if (handlers != null) { - return !handlers.contains(_dispatchBindingEvent); + return !handlers.contains(_dispatchEventToNative); } return true; } @@ -171,7 +257,7 @@ abstract class BindingBridge { Map> eventHandlers = target.getEventHandlers(); List? handlers = eventHandlers[type]; if (handlers != null) { - return handlers.contains(_dispatchBindingEvent); + return handlers.contains(_dispatchEventToNative); } return false; } diff --git a/webf/lib/src/bridge/bridge.dart b/webf/lib/src/bridge/bridge.dart index 1ec9e8baef..868d06d777 100644 --- a/webf/lib/src/bridge/bridge.dart +++ b/webf/lib/src/bridge/bridge.dart @@ -3,11 +3,9 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import 'dart:async'; - import 'package:flutter/foundation.dart'; -import 'package:flutter/scheduler.dart'; import 'package:webf/module.dart'; +import 'package:webf/launcher.dart'; import 'binding.dart'; import 'from_native.dart'; @@ -20,14 +18,11 @@ int kWebFJSPagePoolSize = 1024; bool _firstView = true; /// Init bridge -int initBridge() { +int initBridge(WebFViewController 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(); @@ -37,24 +32,14 @@ int initBridge() { int contextId = -1; - // We should schedule addPersistentFrameCallback() to the next frame because of initBridge() - // will be called from persistent frame callbacks and cause infinity loops. - if (_firstView) { - Future.microtask(() { - // Port flutter's frame callback into bridge. - SchedulerBinding.instance.addPersistentFrameCallback((_) { - flushUICommand(); - flushUICommandCallback(); - }); - }); - } + List dartMethods = makeDartMethodsData(); if (_firstView) { - initJSPagePool(kWebFJSPagePoolSize); + initJSPagePool(kWebFJSPagePoolSize, dartMethods); _firstView = false; contextId = 0; } else { - contextId = allocateNewPage(); + contextId = allocateNewPage(dartMethods); if (contextId == -1) { throw Exception('Can\' allocate new webf bridge: bridge count had reach the maximum size.'); } diff --git a/webf/lib/src/bridge/from_native.dart b/webf/lib/src/bridge/from_native.dart index ad91d39256..5798854a8f 100644 --- a/webf/lib/src/bridge/from_native.dart +++ b/webf/lib/src/bridge/from_native.dart @@ -3,9 +3,8 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import 'dart:convert'; +import 'dart:async'; import 'dart:ffi'; -import 'dart:ui' as ui; import 'dart:typed_data'; import 'package:ffi/ffi.dart'; @@ -48,6 +47,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); @@ -73,61 +78,81 @@ void freeNativeString(Pointer pointer) { // 6. Call from C. // Register InvokeModule -typedef NativeAsyncModuleCallback = Void Function( - Pointer callbackContext, Int32 contextId, Pointer errmsg, Pointer json); -typedef DartAsyncModuleCallback = void Function( - Pointer callbackContext, int contextId, Pointer errmsg, Pointer json); +typedef NativeAsyncModuleCallback = Pointer Function( + Pointer callbackContext, Int32 contextId, Pointer errmsg, Pointer ptr); +typedef DartAsyncModuleCallback = Pointer Function( + Pointer callbackContext, int contextId, Pointer errmsg, Pointer ptr); -typedef NativeInvokeModule = Pointer Function( +typedef NativeInvokeModule = Pointer Function( Pointer callbackContext, Int32 contextId, Pointer module, Pointer method, - Pointer params, + Pointer params, Pointer>); -String invokeModule(Pointer callbackContext, int contextId, String moduleName, String method, String? params, +dynamic invokeModule(Pointer callbackContext, int contextId, String moduleName, String method, params, DartAsyncModuleCallback callback) { WebFController controller = WebFController.getControllerOfJSContextId(contextId)!; - String result = ''; + dynamic result; try { - void invokeModuleCallback({String? error, data}) { + Future invokeModuleCallback({String? error, data}) { + Completer completer = Completer(); // To make sure Promise then() and catch() executed before Promise callback called at JavaScript side. // We should make callback always async. Future.microtask(() { + if (controller.view.disposed) return; + + Pointer callbackResult = nullptr; if (error != null) { Pointer errmsgPtr = error.toNativeUtf8(); - callback(callbackContext, contextId, errmsgPtr, nullptr); + callbackResult = callback(callbackContext, contextId, errmsgPtr, nullptr); malloc.free(errmsgPtr); } else { - Pointer dataPtr = stringToNativeString(jsonEncode(data)); - callback(callbackContext, contextId, nullptr, dataPtr); - freeNativeString(dataPtr); + Pointer dataPtr = malloc.allocate(sizeOf()); + toNativeValue(dataPtr, data); + callbackResult = callback(callbackContext, contextId, nullptr, dataPtr); + malloc.free(dataPtr); + } + if (isEnabledLog) { + print('Invoke module callback from(name: $moduleName method: $method, params: $params) return: ${fromNativeValue(callbackResult)}'); } + + completer.complete(fromNativeValue(callbackResult)); }); + return completer.future; } result = controller.module.moduleManager.invokeModule( - moduleName, method, (params != null && params != '""') ? jsonDecode(params) : null, invokeModuleCallback); + moduleName, method, params, invokeModuleCallback); } catch (e, stack) { + if (isEnabledLog) { + print('Invoke module failed: $e\n$stack'); + } String error = '$e\n$stack'; callback(callbackContext, contextId, error.toNativeUtf8(), nullptr); } + if (isEnabledLog) { + print('Invoke module name: $moduleName method: $method, params: $params return: $result'); + } + return result; } -Pointer _invokeModule( +Pointer _invokeModule( Pointer callbackContext, int contextId, Pointer module, Pointer method, - Pointer params, + Pointer params, Pointer> callback) { - String result = invokeModule(callbackContext, contextId, nativeStringToString(module), nativeStringToString(method), - params == nullptr ? null : nativeStringToString(params), callback.asFunction()); - return stringToNativeString(result); + dynamic result = invokeModule(callbackContext, contextId, nativeStringToString(module), nativeStringToString(method), + fromNativeValue(params), callback.asFunction()); + Pointer returnValue = malloc.allocate(sizeOf()); + toNativeValue(returnValue, result); + return returnValue; } final Pointer> _nativeInvokeModule = Pointer.fromFunction(_invokeModule); @@ -283,15 +308,6 @@ void _cancelAnimationFrame(int contextId, int timerId) { final Pointer> _nativeCancelAnimationFrame = Pointer.fromFunction(_cancelAnimationFrame); -typedef NativeGetScreen = Pointer Function(); - -Pointer _getScreen() { - ui.Size size = ui.window.physicalSize; - return createScreen(size.width / ui.window.devicePixelRatio, size.height / ui.window.devicePixelRatio); -} - -final Pointer> _nativeGetScreen = Pointer.fromFunction(_getScreen); - typedef NativeAsyncBlobCallback = Void Function( Pointer callbackContext, Int32 contextId, Pointer, Pointer, Int32); typedef DartAsyncBlobCallback = void Function( @@ -317,14 +333,14 @@ void _toBlob(Pointer callbackContext, int contextId, Pointer> _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); } @@ -332,24 +348,6 @@ void _flushUICommand() { final Pointer> _nativeFlushUICommand = Pointer.fromFunction(_flushUICommand); -typedef NativeInitWindow = Void Function(Int32 contextId, Pointer nativePtr); -typedef DartInitWindow = void Function(int contextId, Pointer nativePtr); - -void _initWindow(int contextId, Pointer nativePtr) { - WebFViewController.windowNativePtrMap[contextId] = nativePtr; -} - -final Pointer> _nativeInitWindow = Pointer.fromFunction(_initWindow); - -typedef NativeInitDocument = Void Function(Int32 contextId, Pointer nativePtr); -typedef DartInitDocument = void Function(int contextId, Pointer nativePtr); - -void _initDocument(int contextId, Pointer nativePtr) { - WebFViewController.documentNativePtrMap[contextId] = nativePtr; -} - -final Pointer> _nativeInitDocument = Pointer.fromFunction(_initDocument); - typedef NativePerformanceGetEntries = Pointer Function(Int32 contextId); typedef DartPerformanceGetEntries = Pointer Function(int contextId); @@ -400,25 +398,13 @@ final List _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 methodBytes, Int32 length); -typedef DartRegisterDartMethods = void Function(Pointer methodBytes, int length); - -final DartRegisterDartMethods _registerDartMethods = - WebFDynamicLibrary.ref.lookup>('registerDartMethods').asFunction(); - -void registerDartMethodsToCpp() { - Pointer bytes = malloc.allocate(sizeOf() * _dartNativeMethods.length); - Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); - nativeMethodList.setAll(0, _dartNativeMethods); - _registerDartMethods(bytes, _dartNativeMethods.length); +List makeDartMethodsData() { + return _dartNativeMethods; } diff --git a/webf/lib/src/bridge/native_types.dart b/webf/lib/src/bridge/native_types.dart index 12f6129f55..6daeda92b4 100644 --- a/webf/lib/src/bridge/native_types.dart +++ b/webf/lib/src/bridge/native_types.dart @@ -7,7 +7,6 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; -import 'from_native.dart'; import 'native_value.dart'; // MUST READ: @@ -25,179 +24,27 @@ class NativeWebFInfo extends Struct { // We choose to make all this structs have same memory layout. But dart lang did't provide semantically syntax to achieve this (like inheritance a class which extends Struct // or declare struct memory by value). // The only worked ways is use raw bytes to store NativeEvent members. -class RawNativeEvent extends Struct { -// Raw bytes represent the following fields. -// NativeString *type; -// int64_t bubbles; -// int64_t cancelable; -// int64_t timeStamp; -// int64_t defaultPrevented; -// void *target; -// void *currentTarget; +class RawEvent extends Struct { +// Raw bytes represent the NativeEvent fields. external Pointer bytes; @Int64() external int length; + @Int8() + external int is_custom_event; } -class RawNativeInputEvent extends Struct { -// Raw bytes represent the following fields. -// NativeString *type; -// int64_t bubbles; -// int64_t cancelable; -// int64_t timeStamp; -// int64_t defaultPrevented; -// void *target; -// void *currentTarget; -// NativeString *inputType; -// NativeString *data - external Pointer bytes; - @Int64() - external int length; -} - -class RawNativeMediaErrorEvent extends Struct { -// Raw bytes represent the following fields. -// NativeString *type; -// int64_t bubbles; -// int64_t cancelable; -// int64_t timeStamp; -// int64_t defaultPrevented; -// void *target; -// void *currentTarget; -// int64_t code; -// NativeString *message; - external Pointer bytes; - @Int64() - external int length; -} - -class RawNativeMessageEvent extends Struct { -// Raw bytes represent the following fields. -// NativeString *type; -// int64_t bubbles; -// int64_t cancelable; -// int64_t timeStamp; -// int64_t defaultPrevented; -// void *target; -// void *currentTarget; -// NativeString *data; -// NativeString *origin; - external Pointer bytes; - @Int64() - external int length; -} - -// -class RawNativeCustomEvent extends Struct { -// Raw bytes represent the following fields. -// NativeString *type; -// int64_t bubbles; -// int64_t cancelable; -// int64_t timeStamp; -// int64_t defaultPrevented; -// void *target; -// void *currentTarget; -// NativeString *detail; - external Pointer bytes; - @Int64() - external int length; -} - -class RawNativeMouseEvent extends Struct { -// Raw bytes represent the following fields. -// NativeString *type; -// int64_t bubbles; -// int64_t cancelable; -// int64_t timeStamp; -// int64_t defaultPrevented; -// void *target; -// void *currentTarget; -// double clientX; -// double clientY; -// double offsetX; -// double offsetY; - external Pointer bytes; - @Int64() - external int length; -} - -class RawNativeGestureEvent extends Struct { -// Raw bytes represent the following fields. -// NativeString *type; -// int64_t bubbles; -// int64_t cancelable; -// int64_t timeStamp; -// int64_t defaultPrevented; -// void *target; -// void *currentTarget; -// NativeString *state; -// NativeString *direction; -// double deltaX; -// double deltaY; -// double velocityX; -// double velocityY; -// double scale; -// double rotation; - external Pointer bytes; - @Int64() - external int length; -} +class EventDispatchResult extends Struct { + @Bool() + external bool canceled; -class RawNativeCloseEvent extends Struct { -// Raw bytes represent the following fields. -// NativeString *type; -// int64_t bubbles; -// int64_t cancelable; -// int64_t timeStamp; -// int64_t defaultPrevented; -// void *target; -// void *currentTarget; -// int64_t code; -// NativeString *reason; -// int64_t wasClean; - external Pointer bytes; - @Int64() - external int length; -} - -class RawNativeIntersectionChangeEvent extends Struct { -// Raw bytes represent the following fields. -// NativeString *type; -// int64_t bubbles; -// int64_t cancelable; -// int64_t timeStamp; -// int64_t defaultPrevented; -// void *target; -// void *currentTarget; -// double intersectionRatio; - external Pointer bytes; - @Int64() - external int length; + @Bool() + external bool propagationStopped; } -class RawNativeTouchEvent extends Struct { -// Raw bytes represent the following fields. -// NativeString *type; -// int64_t bubbles; -// int64_t cancelable; -// int64_t timeStamp; -// int64_t defaultPrevented; -// void *target; -// void *currentTarget; -// double intersectionRatio; -// NativeTouch **touches; -// int64_t touchLength; -// NativeTouch **targetTouches; -// int64_t targetTouchLength; -// NativeTouch **changedTouches; -// int64_t changedTouchesLength; -// int64_t altKey; -// int64_t metaKey; -// int64_t ctrlKey; -// int64_t shiftKey; - external Pointer bytes; +class NativeTouchList extends Struct { @Int64() external int length; + external Pointer touches; } class NativeTouch extends Struct { @@ -241,51 +88,29 @@ class NativeTouch extends Struct { @Double() external double azimuthAngle; - - @Int64() - external int touchType; } -class NativeBoundingClientRect extends Struct { - @Double() - external double x; - - @Double() - external double y; - - @Double() - external double width; - - @Double() - external double height; - - @Double() - external double top; - - @Double() - external double right; - - @Double() - external double bottom; - - @Double() - external double left; -} +typedef InvokeBindingsMethodsFromNative = Void Function(Pointer binding_object, + Pointer return_value, Pointer method, Int32 argc, Pointer argv); -typedef NativeDispatchEvent = Int32 Function(Int32 contextId, Pointer nativeBindingObject, - Pointer eventType, Pointer nativeEvent, Int32 isCustomEvent); -typedef NativeInvokeBindingMethod = Void Function(Pointer nativePtr, Pointer returnValue, - Pointer method, Int32 argc, Pointer argv); +typedef InvokeBindingMethodsFromDart = Void Function(Pointer binding_object, + Pointer return_value, Pointer method, Int32 argc, Pointer argv); +typedef DartInvokeBindingMethodsFromDart = void Function(Pointer binding_object, + Pointer return_value, Pointer method, int argc, Pointer argv); class NativeBindingObject extends Struct { + @Bool() + external bool disposed; external Pointer instance; - external Pointer> dispatchEvent; + external Pointer> invokeBindingMethodFromDart; // Shared method called by JS side. - external Pointer invokeBindingMethod; + external Pointer> invokeBindingMethodFromNative; } -class NativeCanvasRenderingContext2D extends Struct { - external Pointer invokeBindingMethod; +Pointer allocateNewBindingObject() { + Pointer pointer = malloc.allocate(sizeOf()); + pointer.ref.disposed = false; + return pointer; } class NativePerformanceEntry extends Struct { diff --git a/webf/lib/src/bridge/native_value.dart b/webf/lib/src/bridge/native_value.dart index 43278940a2..2e53e5855c 100644 --- a/webf/lib/src/bridge/native_value.dart +++ b/webf/lib/src/bridge/native_value.dart @@ -2,7 +2,6 @@ * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import 'dart:collection'; import 'dart:convert'; import 'dart:ffi'; @@ -11,14 +10,13 @@ import 'package:webf/bridge.dart'; import 'package:webf/foundation.dart'; class NativeValue extends Struct { - // Or Float64 - @Uint64() + @Int64() external int u; - @Int64() - external int int64; + @Uint32() + external int uint32; - @Int64() + @Int32() external int tag; } @@ -38,34 +36,12 @@ enum JSValueType { enum JSPointerType { AsyncFunctionContext, NativeFunctionContext, - NativeBoundingClientRect, - NativeCanvasRenderingContext2D, - NativeBindingObject + Others } typedef AnonymousNativeFunction = dynamic Function(List args); typedef AsyncAnonymousNativeFunction = Future Function(List args); -int _functionId = 0; -LinkedHashMap _functionMap = LinkedHashMap(); -LinkedHashMap _asyncFunctionMap = LinkedHashMap(); - -AnonymousNativeFunction? getAnonymousNativeFunctionFromId(int id) { - return _functionMap[id]; -} - -AsyncAnonymousNativeFunction? getAsyncAnonymousNativeFunctionFromId(int id) { - return _asyncFunctionMap[id]; -} - -void removeAnonymousNativeFunctionFromId(int id) { - _functionMap.remove(id); -} - -void removeAsyncAnonymousNativeFunctionFromId(int id) { - _asyncFunctionMap.remove(id); -} - dynamic fromNativeValue(Pointer nativeValue) { if (nativeValue == nullptr) return null; @@ -77,28 +53,22 @@ dynamic fromNativeValue(Pointer nativeValue) { freeNativeString(nativeString); return result; case JSValueType.TAG_INT: - return nativeValue.ref.int64; + return nativeValue.ref.u; case JSValueType.TAG_BOOL: - return nativeValue.ref.int64 == 1; + return nativeValue.ref.u == 1; case JSValueType.TAG_NULL: return null; case JSValueType.TAG_FLOAT64: return uInt64ToDouble(nativeValue.ref.u); case JSValueType.TAG_POINTER: - JSPointerType pointerType = JSPointerType.values[nativeValue.ref.int64]; - switch (pointerType) { - case JSPointerType.NativeBoundingClientRect: - return Pointer.fromAddress(nativeValue.ref.u).cast(); - case JSPointerType.NativeCanvasRenderingContext2D: - return Pointer.fromAddress(nativeValue.ref.u).cast(); - case JSPointerType.NativeBindingObject: - return Pointer.fromAddress(nativeValue.ref.u).cast(); - default: - return Pointer.fromAddress(nativeValue.ref.u); - } + return Pointer.fromAddress(nativeValue.ref.u); + case JSValueType.TAG_LIST: + return List.generate(nativeValue.ref.uint32, (index) { + Pointer head = Pointer.fromAddress(nativeValue.ref.u).cast(); + return fromNativeValue(head.elementAt(index)); + }); case JSValueType.TAG_FUNCTION: case JSValueType.TAG_ASYNC_FUNCTION: - case JSValueType.TAG_LIST: break; case JSValueType.TAG_JSON: Pointer nativeString = Pointer.fromAddress(nativeValue.ref.u); @@ -108,59 +78,56 @@ dynamic fromNativeValue(Pointer nativeValue) { } } -void toNativeValue(Pointer target, value) { +void toNativeValue(Pointer target, value, [BindingObject? ownerBindingObject]) { if (value == null) { target.ref.tag = JSValueType.TAG_NULL.index; } else if (value is int) { target.ref.tag = JSValueType.TAG_INT.index; - target.ref.int64 = value; + target.ref.u = value; } else if (value is bool) { target.ref.tag = JSValueType.TAG_BOOL.index; - target.ref.int64 = value ? 1 : 0; + target.ref.u = value ? 1 : 0; } else if (value is double) { target.ref.tag = JSValueType.TAG_FLOAT64.index; - target.ref.u = doubleToUint64(value); - } else if (value is List) { - target.ref.tag = JSValueType.TAG_LIST.index; - target.ref.int64 = value.length; - Pointer> lists = malloc.allocate>(sizeOf() * value.length); - target.ref.u = lists.address; - for(int i = 0; i < value.length; i ++) { - Pointer list_item = malloc.allocate(sizeOf()); - toNativeValue(list_item, value[i]); - lists[i] = list_item; - } + 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) { - target.ref.int64 = JSPointerType.NativeBoundingClientRect.index; - } else if (value is Pointer) { - target.ref.int64 = JSPointerType.NativeCanvasRenderingContext2D.index; - } else if (value is Pointer) { - target.ref.int64 = JSPointerType.NativeBindingObject.index; - } } else if (value is BindingObject) { target.ref.tag = JSValueType.TAG_POINTER.index; - assert(value.pointer is Pointer); - target.ref.u = (value.pointer as Pointer).address; - target.ref.int64 = JSPointerType.NativeBindingObject.index; - } else if (value is AsyncAnonymousNativeFunction) { - int id = _functionId++; - _asyncFunctionMap[id] = value; + target.ref.u = (value.pointer)!.address; + } else if (value is List) { + target.ref.tag = JSValueType.TAG_LIST.index; + target.ref.uint32 = value.length; + Pointer lists = malloc.allocate(sizeOf() * value.length); + target.ref.u = lists.address; + for(int i = 0; i < value.length; i ++) { + toNativeValue(lists.elementAt(i), value[i], ownerBindingObject); + } + } else if (value is AsyncAnonymousNativeFunction && ownerBindingObject != null) { + int id = ownerBindingObject.setAsyncAnonymousNativeFunction(value); target.ref.tag = JSValueType.TAG_ASYNC_FUNCTION.index; - target.ref.int64 = id; - } else if (value is AnonymousNativeFunction) { - int id = _functionId++; - _functionMap[id] = value; + target.ref.u = id; + } else if (value is AnonymousNativeFunction && ownerBindingObject != null) { + int id = ownerBindingObject.setAnonymousNativeFunction(value); target.ref.tag = JSValueType.TAG_FUNCTION.index; - target.ref.int64 = id; + target.ref.u = id; } else if (value is Object) { String str = jsonEncode(value); target.ref.tag = JSValueType.TAG_JSON.index; target.ref.u = str.toNativeUtf8().address; } } + +Pointer makeNativeValueArguments(BindingObject ownerBindingObject, List args) { + Pointer buffer = malloc.allocate(sizeOf() * args.length); + + for(int i = 0; i < args.length; i ++) { + toNativeValue(buffer.elementAt(i), args[i], ownerBindingObject); + } + + return buffer.cast(); +} diff --git a/webf/lib/src/bridge/to_native.dart b/webf/lib/src/bridge/to_native.dart index 64e7c60532..3b4b0340d9 100644 --- a/webf/lib/src/bridge/to_native.dart +++ b/webf/lib/src/bridge/to_native.dart @@ -61,45 +61,35 @@ WebFInfo getWebFInfo() { } // Register invokeEventListener -typedef NativeInvokeEventListener = Void Function( - Int32 contextId, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); -typedef DartInvokeEventListener = void Function( - int contextId, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); +typedef NativeInvokeEventListener = Pointer Function( + Int32 contextId, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); +typedef DartInvokeEventListener = Pointer Function( + int contextId, Pointer, Pointer eventType, Pointer nativeEvent, Pointer); final DartInvokeEventListener _invokeModuleEvent = WebFDynamicLibrary.ref.lookup>('invokeModuleEvent').asFunction(); -void invokeModuleEvent(int contextId, String moduleName, Event? event, String extra) { +dynamic invokeModuleEvent(int contextId, String moduleName, Event? event, extra) { if (WebFController.getControllerOfJSContextId(contextId) == null) { - return; + return null; } Pointer nativeModuleName = stringToNativeString(moduleName); Pointer rawEvent = event == null ? nullptr : event.toRaw().cast(); - _invokeModuleEvent(contextId, nativeModuleName, event == null ? nullptr : event.type.toNativeUtf8(), rawEvent, - stringToNativeString(extra)); + Pointer extraData = malloc.allocate(sizeOf()); + toNativeValue(extraData, extra); + Pointer dispatchResult = _invokeModuleEvent( + contextId, nativeModuleName, event == null ? nullptr : event.type.toNativeUtf8(), rawEvent, extraData); freeNativeString(nativeModuleName); + dynamic result = fromNativeValue(dispatchResult); + malloc.free(dispatchResult); + return result; } typedef DartDispatchEvent = int Function(int contextId, Pointer nativeBindingObject, Pointer eventType, Pointer nativeEvent, int isCustomEvent); -void emitUIEvent(int contextId, Pointer nativeBindingObject, Event event) { - if (WebFController.getControllerOfJSContextId(contextId) == null) { - return; - } - DartDispatchEvent dispatchEvent = nativeBindingObject.ref.dispatchEvent.asFunction(); - Pointer rawEvent = event.toRaw().cast(); - bool isCustomEvent = event is CustomEvent; - Pointer 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); -} - -void emitModuleEvent(int contextId, String moduleName, Event? event, String extra) { - invokeModuleEvent(contextId, moduleName, event, extra); +dynamic emitModuleEvent(int contextId, String moduleName, Event? event, extra) { + return invokeModuleEvent(contextId, moduleName, event, extra); } // Register createScreen @@ -130,6 +120,7 @@ final DartParseHTML _parseHTML = WebFDynamicLibrary.ref.lookup>('parseHTML').asFunction(); int _anonymousScriptEvaluationId = 0; + void evaluateScripts(int contextId, String code, {String? url, int line = 0}) { if (WebFController.getControllerOfJSContextId(contextId) == null) { return; @@ -181,14 +172,18 @@ void parseHTML(int contextId, String code) { } // Register initJsEngine -typedef NativeInitJSPagePool = Void Function(Int32 poolSize); -typedef DartInitJSPagePool = void Function(int poolSize); +typedef NativeInitJSPagePool = Void Function(Int32 poolSize, Pointer dartMethods, Int32 methodsLength); +typedef DartInitJSPagePool = void Function(int poolSize, Pointer dartMethods, int length); final DartInitJSPagePool _initJSPagePool = WebFDynamicLibrary.ref.lookup>('initJSPagePool').asFunction(); -void initJSPagePool(int poolSize) { - _initJSPagePool(poolSize); +void initJSPagePool(int poolSize, List dartMethods) { + Pointer bytes = malloc.allocate(sizeOf() * dartMethods.length); + Uint64List nativeMethodList = bytes.asTypedList(dartMethods.length); + nativeMethodList.setAll(0, dartMethods); + + _initJSPagePool(poolSize, bytes, dartMethods.length); } typedef NativeDisposePage = Void Function(Int32 contextId); @@ -201,14 +196,18 @@ void disposePage(int contextId) { _disposePage(contextId); } -typedef NativeAllocateNewPage = Int32 Function(Int32); -typedef DartAllocateNewPage = int Function(int); +typedef NativeAllocateNewPage = Int32 Function(Int32, Pointer dartMethods, Int32 methodsLength); +typedef DartAllocateNewPage = int Function(int, Pointer dartMethods, int methodsLength); final DartAllocateNewPage _allocateNewPage = WebFDynamicLibrary.ref.lookup>('allocateNewPage').asFunction(); -int allocateNewPage([int targetContextId = -1]) { - return _allocateNewPage(targetContextId); +int allocateNewPage(List dartMethods, [int targetContextId = -1]) { + Pointer bytes = malloc.allocate(sizeOf() * dartMethods.length); + Uint64List nativeMethodList = bytes.asTypedList(dartMethods.length); + nativeMethodList.setAll(0, dartMethods); + + return _allocateNewPage(targetContextId, bytes, dartMethods.length); } typedef NativeRegisterPluginByteCode = Void Function(Pointer bytes, Int32 length, Pointer pluginName); @@ -236,31 +235,24 @@ bool profileModeEnabled() { } // Regisdster reloadJsContext -typedef NativeReloadJSContext = Void Function(Int32 contextId); -typedef DartReloadJSContext = void Function(int contextId); +typedef NativeReloadJSContext = Void Function(Int32 contextId, Pointer dartMethods, Int32 methodsLength); +typedef DartReloadJSContext = void Function(int contextId, Pointer dartMethods, int methodsLength); final DartReloadJSContext _reloadJSContext = WebFDynamicLibrary.ref.lookup>('reloadJsContext').asFunction(); -Future reloadJSContext(int contextId) async { +Future reloadJSContext(int contextId, List dartMethods) async { Completer completer = Completer(); Future.microtask(() { - _reloadJSContext(contextId); + Pointer bytes = malloc.allocate(sizeOf() * dartMethods.length); + Uint64List nativeMethodList = bytes.asTypedList(dartMethods.length); + nativeMethodList.setAll(0, dartMethods); + _reloadJSContext(contextId, bytes, dartMethods.length); completer.complete(); }); return completer.future; } -typedef NativeFlushUICommandCallback = Void Function(); -typedef DartFlushUICommandCallback = void Function(); - -final DartFlushUICommandCallback _flushUICommandCallback = - WebFDynamicLibrary.ref.lookup>('flushUICommandCallback').asFunction(); - -void flushUICommandCallback() { - _flushUICommandCallback(); -} - typedef NativeDispatchUITask = Void Function(Int32 contextId, Pointer context, Pointer callback); typedef DartDispatchUITask = void Function(int contextId, Pointer context, Pointer callback); @@ -275,6 +267,8 @@ enum UICommandType { createElement, createTextNode, createComment, + createDocument, + createWindow, disposeEventTarget, addEvent, removeNode, @@ -348,7 +342,7 @@ const int args01StringMemOffset = 2; const int args02StringMemOffset = 3; const int nativePtrMemOffset = 4; -final bool isEnabledLog = kDebugMode && Platform.environment['ENABLE_WEBF_JS_LOG'] == 'true'; +final bool isEnabledLog = !kReleaseMode && Platform.environment['ENABLE_WEBF_JS_LOG'] == 'true'; // We found there are performance bottleneck of reading native memory with Dart FFI API. // So we align all UI instructions to a whole block of memory, and then convert them into a dart array at one time, @@ -419,104 +413,113 @@ void clearUICommand(int contextId) { _clearUICommandItems(contextId); } -void flushUICommand() { - Map controllerMap = WebFController.getControllerMap(); - for (WebFController? controller in controllerMap.values) { - if (controller == null) continue; - Pointer nativeCommandItems = _getUICommandItems(controller.view.contextId); - int commandLength = _getUICommandItemSize(controller.view.contextId); +void flushUICommandWithContextId(int contextId) { + WebFController? controller = WebFController.getControllerOfJSContextId(contextId); + if (controller != null) { + flushUICommand(controller.view); + } +} - if (commandLength == 0 || nativeCommandItems == nullptr) { - continue; - } +void flushUICommand(WebFViewController view) { + Pointer nativeCommandItems = _getUICommandItems(view.contextId); + int commandLength = _getUICommandItemSize(view.contextId); - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_START); - } + if (commandLength == 0 || nativeCommandItems == nullptr) { + return; + } - List commands = readNativeUICommandToDart(nativeCommandItems, commandLength, controller.view.contextId); + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_START); + } - SchedulerBinding.instance.scheduleFrame(); + List commands = readNativeUICommandToDart(nativeCommandItems, commandLength, view.contextId); - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_END); - } + SchedulerBinding.instance.scheduleFrame(); + + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_END); + } - Map 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(), command.args[0]); - break; - case UICommandType.createTextNode: - controller.view.createTextNode(id, nativePtr.cast(), command.args[0]); - break; - case UICommandType.createComment: - controller.view.createComment(id, nativePtr.cast()); - 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()); - break; - default: - break; - } - } catch (e, stack) { - print('$e\n$stack'); + Map 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(), command.args[0]); + break; + case UICommandType.createDocument: + view.initDocument(id, nativePtr.cast()); + break; + case UICommandType.createWindow: + view.initWindow(id, nativePtr.cast()); + break; + case UICommandType.createTextNode: + view.createTextNode(id, nativePtr.cast(), command.args[0]); + break; + case UICommandType.createComment: + view.createComment(id, nativePtr.cast()); + break; + case UICommandType.disposeEventTarget: + view.disposeEventTarget(id, nativePtr.cast()); + 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()); + 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/webf/lib/src/css/css_animation.dart b/webf/lib/src/css/css_animation.dart index 6fdde2b735..d101c5ea50 100644 --- a/webf/lib/src/css/css_animation.dart +++ b/webf/lib/src/css/css_animation.dart @@ -176,7 +176,7 @@ mixin CSSAnimationMixin on RenderStyle { final fillMode = camelize(_getSingleString(animationFillMode, i)); EffectTiming? options = EffectTiming( - duration: CSSTime.parseTime(duration)!.toDouble(), + duration: CSSTime.parseNotNegativeTime(duration)!.toDouble(), easing: timingFunction, delay: CSSTime.parseTime(delay)!.toDouble(), fill: FillMode.values.firstWhere((element) { diff --git a/webf/lib/src/css/overflow.dart b/webf/lib/src/css/overflow.dart index 947791e2d8..1e0a55811c 100644 --- a/webf/lib/src/css/overflow.dart +++ b/webf/lib/src/css/overflow.dart @@ -377,48 +377,48 @@ mixin ElementOverflowMixin on ElementBase { _scrollTo(x: value); } - int get scrollHeight { + double get scrollHeight { WebFScrollable? scrollable = _getScrollable(Axis.vertical); if (scrollable?.position?.maxScrollExtent != null) { // Viewport height + maxScrollExtent - return renderBoxModel!.clientHeight + scrollable!.position!.maxScrollExtent.toInt(); + return renderBoxModel!.clientHeight + scrollable!.position!.maxScrollExtent; } Size scrollContainerSize = renderBoxModel!.scrollableSize; - return scrollContainerSize.height.toInt(); + return scrollContainerSize.height; } - int get scrollWidth { + double get scrollWidth { WebFScrollable? scrollable = _getScrollable(Axis.horizontal); if (scrollable?.position?.maxScrollExtent != null) { - return renderBoxModel!.clientWidth + scrollable!.position!.maxScrollExtent.toInt(); + return renderBoxModel!.clientWidth + scrollable!.position!.maxScrollExtent; } Size scrollContainerSize = renderBoxModel!.scrollableSize; - return scrollContainerSize.width.toInt(); + return scrollContainerSize.width; } - int get clientTop => renderBoxModel?.renderStyle.effectiveBorderTopWidth.computedValue.toInt() ?? 0; + double get clientTop => renderBoxModel?.renderStyle.effectiveBorderTopWidth.computedValue ?? 0.0; - int get clientLeft => renderBoxModel?.renderStyle.effectiveBorderLeftWidth.computedValue.toInt() ?? 0; + double get clientLeft => renderBoxModel?.renderStyle.effectiveBorderLeftWidth.computedValue ?? 0.0; - int get clientWidth => renderBoxModel?.clientWidth ?? 0; + double get clientWidth => renderBoxModel?.clientWidth ?? 0.0; - int get clientHeight => renderBoxModel?.clientHeight ?? 0; + double get clientHeight => renderBoxModel?.clientHeight ?? 0.0; - int get offsetWidth { + double get offsetWidth { RenderBoxModel? renderBox = renderBoxModel; if (renderBox == null) { return 0; } - return renderBox.hasSize ? renderBox.size.width.toInt() : 0; + return renderBox.hasSize ? renderBox.size.width : 0.0; } - int get offsetHeight { + double get offsetHeight { RenderBoxModel? renderBox = renderBoxModel; if (renderBox == null) { return 0; } - return renderBox.hasSize ? renderBox.size.height.toInt() : 0; + return renderBox.hasSize ? renderBox.size.height : 0.0; } void _scrollBy({double dx = 0.0, double dy = 0.0, bool? withAnimation}) { diff --git a/webf/lib/src/css/transition.dart b/webf/lib/src/css/transition.dart index c3858d80d7..3254b6659b 100644 --- a/webf/lib/src/css/transition.dart +++ b/webf/lib/src/css/transition.dart @@ -385,7 +385,7 @@ mixin CSSTransitionMixin on RenderStyle { // [duration, function, delay] if (transitionOptions != null) { return EffectTiming( - duration: CSSTime.parseTime(transitionOptions[0])!.toDouble(), + duration: CSSTime.parseNotNegativeTime(transitionOptions[0])!.toDouble(), easing: transitionOptions[1], delay: CSSTime.parseTime(transitionOptions[2])!.toDouble(), // In order for CSS Transitions to be seeked backwards, they need to have their fill mode set to backwards diff --git a/webf/lib/src/css/values/time.dart b/webf/lib/src/css/values/time.dart index 59f5828167..74dcabfe1f 100644 --- a/webf/lib/src/css/values/time.dart +++ b/webf/lib/src/css/values/time.dart @@ -19,16 +19,35 @@ class CSSTime { return (value == _0s || value == _0ms || _timeRegExp.firstMatch(value) != null); } - static int? parseTime(String input) { - if (_cachedParsedTime.containsKey(input)) { - return _cachedParsedTime[input]; - } + static int? _parseTimeValue(String input) { int? milliseconds; if (input.endsWith(MILLISECONDS)) { milliseconds = double.tryParse(input.split(MILLISECONDS)[0])!.toInt(); } else if (input.endsWith(SECOND)) { milliseconds = (double.tryParse(input.split(SECOND)[0])! * 1000).toInt(); } + return milliseconds; + } + + static int? parseTime(String input) { + if (_cachedParsedTime.containsKey(input)) { + return _cachedParsedTime[input]; + } + + int? milliseconds = _parseTimeValue(input); + + return _cachedParsedTime[input] = milliseconds; + } + + static int? parseNotNegativeTime(String input) { + if (_cachedParsedTime.containsKey(input)) { + return _cachedParsedTime[input]; + } + + int? milliseconds = _parseTimeValue(input); + if (milliseconds != null && milliseconds < 0) { + milliseconds = 0; + } return _cachedParsedTime[input] = milliseconds; } diff --git a/webf/lib/src/devtools/server.dart b/webf/lib/src/devtools/server.dart index 33cef48139..53b6d2fe0c 100644 --- a/webf/lib/src/devtools/server.dart +++ b/webf/lib/src/devtools/server.dart @@ -71,9 +71,6 @@ void attachInspector(int contextId) { } void initInspectorServerNativeBinding(int contextId) { - final DartRegisterDartMethods _registerInspectorServerDartMethods = WebFDynamicLibrary.ref - .lookup>('registerInspectorDartMethods') - .asFunction(); final Pointer> _nativeInspectorMessage = Pointer.fromFunction(_onInspectorMessage); final Pointer> _nativeRegisterInspectorMessageCallback = @@ -90,8 +87,6 @@ void initInspectorServerNativeBinding(int contextId) { Pointer bytes = malloc.allocate(_dartNativeMethods.length * sizeOf()); Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); - - _registerInspectorServerDartMethods(bytes, _dartNativeMethods.length); } void serverIsolateEntryPoint(SendPort isolateToMainStream) { diff --git a/webf/lib/src/devtools/service.dart b/webf/lib/src/devtools/service.dart index 49c58fac6a..5a5bd339ce 100644 --- a/webf/lib/src/devtools/service.dart +++ b/webf/lib/src/devtools/service.dart @@ -5,26 +5,18 @@ import 'dart:isolate'; import 'dart:ffi'; -import 'dart:typed_data'; - -import 'package:ffi/ffi.dart'; import 'package:webf/webf.dart'; import 'package:webf/devtools.dart'; typedef NativePostTaskToInspectorThread = Void Function(Int32 contextId, Pointer context, Pointer callback); typedef DartPostTaskToInspectorThread = void Function(int contextId, Pointer context, Pointer callback); -void _postTaskToInspectorThread(int contextId, Pointer context, Pointer callback) { - ChromeDevToolsService? devTool = ChromeDevToolsService.getDevToolOfContextId(contextId); - if (devTool != null) { - devTool.isolateServerPort!.send(InspectorPostTaskMessage(context.address, callback.address)); - } -} - -final Pointer> _nativePostTaskToInspectorThread = - Pointer.fromFunction(_postTaskToInspectorThread); - -final List _dartNativeMethods = [_nativePostTaskToInspectorThread.address]; +// void _postTaskToInspectorThread(int contextId, Pointer context, Pointer callback) { +// ChromeDevToolsService? devTool = ChromeDevToolsService.getDevToolOfContextId(contextId); +// if (devTool != null) { +// devTool.isolateServerPort!.send(InspectorPostTaskMessage(context.address, callback.address)); +// } +// } void spawnIsolateInspectorServer(ChromeDevToolsService devTool, WebFController controller, {int port = INSPECTOR_DEFAULT_PORT, String? address}) { @@ -113,16 +105,4 @@ class ChromeDevToolsService extends DevToolsService { controller!.view.debugDOMTreeChanged = _uiInspector!.onDOMTreeChanged; _isolateServerPort!.send(InspectorReload(_controller!.view.contextId)); } - - // @TODO: Implement and remove. - // ignore: unused_element - static bool _registerUIDartMethodsToCpp() { - final DartRegisterDartMethods _registerDartMethods = - WebFDynamicLibrary.ref.lookup>('registerUIDartMethods').asFunction(); - Pointer bytes = malloc.allocate(_dartNativeMethods.length * sizeOf()); - Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); - nativeMethodList.setAll(0, _dartNativeMethods); - _registerDartMethods(bytes, _dartNativeMethods.length); - return true; - } } diff --git a/webf/lib/src/dom/bounding_client_rect.dart b/webf/lib/src/dom/bounding_client_rect.dart index cb7a58446c..cce811598c 100644 --- a/webf/lib/src/dom/bounding_client_rect.dart +++ b/webf/lib/src/dom/bounding_client_rect.dart @@ -4,11 +4,11 @@ */ import 'dart:ffi'; -import 'package:ffi/ffi.dart'; import 'package:webf/bridge.dart'; +import 'package:webf/foundation.dart'; -class BoundingClientRect { - static const BoundingClientRect zero = BoundingClientRect(0, 0, 0, 0, 0, 0, 0, 0); +class BoundingClientRect extends BindingObject { + static BoundingClientRect zero = BoundingClientRect(0, 0, 0, 0, 0, 0, 0, 0); final double x; final double y; @@ -19,20 +19,35 @@ class BoundingClientRect { final double bottom; final double left; - const BoundingClientRect(this.x, this.y, this.width, this.height, this.top, this.right, this.bottom, this.left); - - Pointer toNative() { - Pointer nativeBoundingClientRect = - malloc.allocate(sizeOf()); - nativeBoundingClientRect.ref.width = width; - nativeBoundingClientRect.ref.height = height; - nativeBoundingClientRect.ref.x = x; - nativeBoundingClientRect.ref.y = y; - nativeBoundingClientRect.ref.top = top; - nativeBoundingClientRect.ref.right = right; - nativeBoundingClientRect.ref.left = left; - nativeBoundingClientRect.ref.bottom = bottom; - return nativeBoundingClientRect; + BoundingClientRect(this.x, this.y, this.width, this.height, this.top, this.right, this.bottom, this.left) + : _pointer = allocateNewBindingObject(), + super(); + + final Pointer _pointer; + + @override + get pointer => _pointer; + + @override + dynamic getBindingProperty(String key) { + switch(key) { + case 'x': + return x; + case 'y': + return y; + case 'width': + return width; + case 'height': + return height; + case 'left': + return left; + case 'right': + return right; + case 'top': + return top; + case 'bottom': + return bottom; + } } Map toJSON() { diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 261c6f05f2..2b4195abda 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -13,6 +13,7 @@ import 'package:webf/module.dart'; import 'package:webf/rendering.dart'; import 'package:webf/src/css/query_selector.dart' as QuerySelector; import 'package:webf/src/dom/element_registry.dart' as element_registry; +import 'package:webf/src/foundation/cookie_jar.dart'; import 'package:webf/widget.dart'; class Document extends Node { @@ -53,6 +54,8 @@ class Document extends Node { Element? focusedElement; + CookieJar cookie_ = CookieJar(); + // Returns the Window object of the active document. // https://html.spec.whatwg.org/multipage/window-object.html#dom-document-defaultview-dev Window get defaultView => controller.view.window; @@ -94,6 +97,27 @@ class Document extends Node { } } + @override + void setBindingProperty(String key, value) { + switch(key) { + case 'cookie': + cookie_.setCookie(value); + break; + } + + super.setBindingProperty(key, value); + } + + @override + getBindingProperty(String key) { + switch(key) { + case 'cookie': + return cookie_.cookie(); + } + + return super.getBindingProperty(key); + } + @override invokeBindingMethod(String method, List args) { switch (method) { @@ -101,24 +125,49 @@ class Document extends Node { return querySelectorAll(args); case 'querySelector': return querySelector(args); + case 'getElementById': + return getElementById(args); + case 'getElementsByClassName': + return getElementsByClassName(args); + case 'getElementsByTagName': + return getElementsByTagName(args); + case 'getElementsByName': + return getElementsByName(args); } return super.invokeBindingMethod(method, args); } dynamic querySelector(List args) { - if (args.isEmpty || args.first is! String) { - return null; - } + if (args[0].runtimeType == String && (args[0] as String).isEmpty) return null; return QuerySelector.querySelector(this, args.first); } dynamic querySelectorAll(List args) { - if (args.isEmpty || args.first is! String) { - return null; - } + if (args[0].runtimeType == String && (args[0] as String).isEmpty) return []; return QuerySelector.querySelectorAll(this, args.first); } + dynamic getElementById(List args) { + if (args[0].runtimeType == String && (args[0] as String).isEmpty) return null; + return QuerySelector.querySelector(this, '#' + args.first); + } + + dynamic getElementsByClassName(List args) { + if (args[0].runtimeType == String && (args[0] as String).isEmpty) return []; + String selector = (args.first as String).split(classNameSplitRegExp).map((e) => '.' + e).join(''); + return QuerySelector.querySelectorAll(this, selector); + } + + dynamic getElementsByTagName(List args) { + if (args[0].runtimeType == String && (args[0] as String).isEmpty) return []; + return QuerySelector.querySelectorAll(this, args.first); + } + + dynamic getElementsByName(List args) { + if (args[0].runtimeType == String && (args[0] as String).isEmpty) return []; + return QuerySelector.querySelectorAll(this, '[name="${args.first}"]'); + } + Element? _documentElement; Element? get documentElement => _documentElement; set documentElement(Element? element) { @@ -224,7 +273,7 @@ class Document extends Node { bool _recalculating = false; void updateStyleIfNeeded() { - if (!styleNodeManager.hasPendingStyleSheet) { + if (!styleNodeManager.hasPendingStyleSheet && !styleNodeManager.isStyleSheetCandidateNodeChanged) { return; } if (_recalculating) { diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 8106b7c976..bfedd1aa00 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -15,8 +15,9 @@ import 'package:webf/dom.dart'; import 'package:webf/module.dart' hide EMPTY_STRING; import 'package:webf/foundation.dart'; import 'package:webf/rendering.dart'; +import 'package:webf/src/css/query_selector.dart' as QuerySelector; -final RegExp _splitRegExp = RegExp(r'\s+'); +final RegExp classNameSplitRegExp = RegExp(r'\s+'); const String _ONE_SPACE = ' '; const String _STYLE_PROPERTY = 'style'; const String _ID = 'id'; @@ -50,7 +51,9 @@ enum BoxSizeType { mixin ElementBase on Node { RenderLayoutBox? _renderLayoutBox; RenderReplaced? _renderReplaced; + RenderBoxModel? get renderBoxModel => _renderLayoutBox ?? _renderReplaced; + set renderBoxModel(RenderBoxModel? value) { if (value == null) { _renderReplaced = null; @@ -112,7 +115,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element set className(String className) { _classList.clear(); - List classList = className.split(_splitRegExp); + List classList = className.split(classNameSplitRegExp); if (classList.isNotEmpty) { _classList.addAll(classList); } @@ -143,6 +146,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element } bool _forceToRepaintBoundary = false; + set forceToRepaintBoundary(bool value) { if (_forceToRepaintBoundary == value) { return; @@ -235,6 +239,8 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element case 'clientHeight': return clientHeight; + case 'id': + return id; case 'className': return className; case 'classList': @@ -258,7 +264,9 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element case 'className': className = castToType(value); break; - + case 'id': + id = castToType(value); + break; default: super.setBindingProperty(key, value); } @@ -268,7 +276,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element invokeBindingMethod(String method, List args) { switch (method) { case 'getBoundingClientRect': - return getBoundingClientRect().toNative(); + return getBoundingClientRect(); case 'scroll': return scroll(castToType(args[0]), castToType(args[1])); case 'scrollBy': @@ -277,12 +285,24 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element return scrollTo(castToType(args[0]), castToType(args[1])); case 'click': return click(); + case 'getElementsByClassName': + return getElementsByClassName(args); + case 'getElementsByTagName': + return getElementsByTagName(args); default: super.invokeBindingMethod(method, args); } } + dynamic getElementsByClassName(List args) { + return QuerySelector.querySelectorAll(this, '.' + args.first); + } + + dynamic getElementsByTagName(List args) { + return QuerySelector.querySelectorAll(this, args.first); + } + void _updateRenderBoxModel() { RenderBoxModel nextRenderBoxModel; if (_isReplacedElement) { @@ -541,11 +561,12 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element BoundingClientRect getBoundingClientRect() => boundingClientRect; bool _shouldConsumeScrollTicker = false; + void _consumeScrollTicker(_) { if (_shouldConsumeScrollTicker && hasEventListener(EVENT_SCROLL)) { _dispatchScrollEvent(); - _shouldConsumeScrollTicker = false; } + _shouldConsumeScrollTicker = false; } /// https://drafts.csswg.org/cssom-view/#scrolling-events @@ -1003,6 +1024,8 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element _removeInlineStyle(); } else if (qualifiedName == _CLASS_NAME) { className = EMPTY_STRING; + } else if (qualifiedName == _ID) { + id = EMPTY_STRING; } attributes.remove(qualifiedName); } @@ -1582,26 +1605,26 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element // The HTMLElement.offsetLeft read-only property returns the number of pixels that the upper left corner // of the current element is offset to the left within the HTMLElement.offsetParent node. // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsetleft - int get offsetLeft { - int offset = 0; + double get offsetLeft { + double offset = 0.0; if (!isRendererAttached) { return offset; } Offset relative = _getOffset(renderBoxModel!, ancestor: offsetParent); - offset += relative.dx.toInt(); + offset += relative.dx; return offset; } // The HTMLElement.offsetTop read-only property returns the distance of the outer border // of the current element relative to the inner border of the top of the offsetParent node. // https://drafts.csswg.org/cssom-view/#dom-htmlelement-offsettop - int get offsetTop { - int offset = 0; + double get offsetTop { + double offset = 0.0; if (!isRendererAttached) { return offset; } Offset relative = _getOffset(renderBoxModel!, ancestor: offsetParent); - offset += relative.dy.toInt(); + offset += relative.dy; return offset; } @@ -1644,7 +1667,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element void click() { flushLayout(); - Event clickEvent = MouseEvent(EVENT_CLICK, MouseEventInit(bubbles: true, cancelable: true)); + Event clickEvent = MouseEvent(EVENT_CLICK, detail: 1, view: ownerDocument.defaultView); // If element not in tree, click is fired and only response to itself. dispatchEvent(clickEvent); } diff --git a/webf/lib/src/dom/elements/canvas/canvas.dart b/webf/lib/src/dom/elements/canvas/canvas.dart index c6d86fc8b8..70383120fa 100644 --- a/webf/lib/src/dom/elements/canvas/canvas.dart +++ b/webf/lib/src/dom/elements/canvas/canvas.dart @@ -83,7 +83,7 @@ class CanvasElement extends Element { invokeBindingMethod(String method, List args) { switch (method) { case 'getContext': - return getContext(castToType(args[0])).toNative(); + return getContext(castToType(args[0])); default: return super.invokeBindingMethod(method, args); } diff --git a/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart b/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart index 046acd0fc6..ffa0bb2aae 100644 --- a/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart +++ b/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart @@ -7,7 +7,6 @@ import 'dart:typed_data'; import 'dart:ui'; import 'dart:ffi' as ffi; -import 'package:ffi/ffi.dart'; import 'package:flutter/painting.dart'; import 'package:webf/bridge.dart'; import 'package:webf/foundation.dart'; @@ -45,10 +44,10 @@ typedef CanvasAction = void Function(Canvas, Size); class CanvasRenderingContext2D extends BindingObject { CanvasRenderingContext2D(this.canvas) - : _pointer = malloc.allocate(ffi.sizeOf()), + : _pointer = allocateNewBindingObject(), super(); - final ffi.Pointer _pointer; + final ffi.Pointer _pointer; @override get pointer => _pointer; @@ -56,10 +55,6 @@ class CanvasRenderingContext2D extends BindingObject { @override get contextId => canvas.contextId; - ffi.Pointer toNative() { - return pointer; - } - @override invokeBindingMethod(String method, List args) { // @NOTE: Bridge not guarantee that input type number is double. @@ -71,7 +66,7 @@ class CanvasRenderingContext2D extends BindingObject { castToType(args[2]).toDouble(), castToType(args[3]).toDouble(), castToType(args[4]).toDouble(), - anticlockwise: args[5] == 1 ? true : false); + anticlockwise: (args.length > 5 && args[5] == 1) ? true : false); case 'arcTo': return arcTo( castToType(args[0]).toDouble(), @@ -89,21 +84,29 @@ class CanvasRenderingContext2D extends BindingObject { return strokeRect(castToType(args[0]).toDouble(), castToType(args[1]).toDouble(), castToType(args[2]).toDouble(), castToType(args[3]).toDouble()); case 'fillText': - double maxWidth = castToType(args[3]).toDouble(); - if (!maxWidth.isNaN) { + if (args.length > 3) { + double maxWidth = castToType(args[3]).toDouble(); + if (!maxWidth.isNaN) { + return fillText( + castToType(args[0]), castToType(args[1]).toDouble(), castToType(args[2]).toDouble(), + maxWidth: maxWidth); + } return fillText( - castToType(args[0]), castToType(args[1]).toDouble(), castToType(args[2]).toDouble(), - maxWidth: maxWidth); + castToType(args[0]), castToType(args[1]).toDouble(), castToType(args[2]).toDouble()); } else { return fillText( castToType(args[0]), castToType(args[1]).toDouble(), castToType(args[2]).toDouble()); } case 'strokeText': - double maxWidth = castToType(args[3]).toDouble(); - if (!maxWidth.isNaN) { + if (args.length > 3) { + double maxWidth = castToType(args[3]).toDouble(); + if (!maxWidth.isNaN) { + return strokeText( + castToType(args[0]), castToType(args[1]).toDouble(), castToType(args[2]).toDouble(), + maxWidth: maxWidth); + } return strokeText( - castToType(args[0]), castToType(args[1]).toDouble(), castToType(args[2]).toDouble(), - maxWidth: maxWidth); + castToType(args[0]), castToType(args[1]).toDouble(), castToType(args[2]).toDouble()); } else { return strokeText( castToType(args[0]), castToType(args[1]).toDouble(), castToType(args[2]).toDouble()); @@ -123,7 +126,7 @@ class CanvasRenderingContext2D extends BindingObject { castToType(args[4]).toDouble(), castToType(args[5]).toDouble()); case 'clip': - PathFillType fillType = castToType(args[0]) == EVENODD ? PathFillType.evenOdd : PathFillType.nonZero; + PathFillType fillType = (args.isNotEmpty && castToType(args[0]) == EVENODD) ? PathFillType.evenOdd : PathFillType.nonZero; return clip(fillType); case 'closePath': return closePath(); @@ -163,9 +166,9 @@ class CanvasRenderingContext2D extends BindingObject { castToType(args[4]).toDouble(), castToType(args[5]).toDouble(), castToType(args[6]).toDouble(), - anticlockwise: args[7] == 1 ? true : false); + anticlockwise: (args.length > 7 && args[7] == 1) ? true : false); case 'fill': - PathFillType fillType = args[0] == EVENODD ? PathFillType.evenOdd : PathFillType.nonZero; + PathFillType fillType = (args.isNotEmpty && args[0] == EVENODD) ? PathFillType.evenOdd : PathFillType.nonZero; return fill(fillType); case 'lineTo': return lineTo(castToType(args[0]).toDouble(), castToType(args[1]).toDouble()); diff --git a/webf/lib/src/dom/elements/img.dart b/webf/lib/src/dom/elements/img.dart index d0588e32c3..dbe009582b 100644 --- a/webf/lib/src/dom/elements/img.dart +++ b/webf/lib/src/dom/elements/img.dart @@ -7,6 +7,7 @@ import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; import 'package:webf/css.dart'; import 'package:webf/dom.dart'; import 'package:webf/foundation.dart'; @@ -490,7 +491,10 @@ class ImageElement extends Element { // Fire the load event at first frame come. if (_frameCount == 1 && !_loaded) { _loaded = true; - scheduleMicrotask(_dispatchLoadEvent); + SchedulerBinding.instance.addPostFrameCallback((timeStamp) { + _dispatchLoadEvent(); + }); + SchedulerBinding.instance.scheduleFrame(); } } diff --git a/webf/lib/src/dom/elements/text_form_control.dart b/webf/lib/src/dom/elements/text_form_control.dart index a3e393f770..60f5d79477 100644 --- a/webf/lib/src/dom/elements/text_form_control.dart +++ b/webf/lib/src/dom/elements/text_form_control.dart @@ -1075,7 +1075,7 @@ class TextFormControlElement extends Element implements TextInputClient, TickerP String inputData = ''; // https://www.w3.org/TR/input-events-1/#interface-InputEvent-Attributes String inputType = ''; - InputEvent inputEvent = InputEvent(inputData, inputType: inputType); + InputEvent inputEvent = InputEvent(inputType: inputType, data: inputData); dispatchEvent(inputEvent); hasDirtyValue = true; } diff --git a/webf/lib/src/dom/event.dart b/webf/lib/src/dom/event.dart index ab2f775190..3c1966d8e1 100644 --- a/webf/lib/src/dom/event.dart +++ b/webf/lib/src/dom/event.dart @@ -155,8 +155,15 @@ mixin ElementEventMixin on ElementBase { /// reference: https://developer.mozilla.org/zh-CN/docs/Web/API/Event class Event { String type; - bool bubbles = false; - bool cancelable = false; + + // A boolean value indicating whether the event bubbles. The default is false. + bool bubbles; + + // A boolean value indicating whether the event can be cancelled. The default is false. + bool cancelable; + + // A boolean value indicating whether the event will trigger listeners outside of a shadow root (see Event.composed for more details). + bool composed; EventTarget? currentTarget; EventTarget? target; int timeStamp = DateTime.now().millisecondsSinceEpoch; @@ -164,12 +171,12 @@ class Event { bool _immediateBubble = true; bool propagationStopped = false; - Event(this.type, [EventInit? init]) { - init ??= EventInit(); - - bubbles = init.bubbles; - cancelable = init.cancelable; - } + Event( + this.type, { + this.bubbles = false, + this.cancelable = false, + this.composed = false, + }); void preventDefault() { if (cancelable) { @@ -178,6 +185,7 @@ class Event { } bool canBubble() => _immediateBubble; + void stopImmediatePropagation() { _immediateBubble = false; } @@ -186,8 +194,8 @@ class Event { bubbles = false; } - Pointer toRaw([int extraLength = 0]) { - Pointer event = malloc.allocate(sizeOf()); + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { + Pointer event = malloc.allocate(sizeOf()); EventTarget? _target = target; EventTarget? _currentTarget = currentTarget; @@ -196,18 +204,21 @@ class Event { stringToNativeString(type).address, bubbles ? 1 : 0, cancelable ? 1 : 0, + composed ? 1 : 0, timeStamp, defaultPrevented ? 1 : 0, (_target != null && _target.pointer != null) ? _target.pointer!.address : nullptr.address, (_currentTarget != null && _currentTarget.pointer != null) ? _currentTarget.pointer!.address : nullptr.address, ]; - int totalLength = methods.length + extraLength; + // Allocate extra bytes to store subclass's members. + int nativeStructSize = methods.length + extraLength; - final Pointer bytes = malloc.allocate(totalLength * sizeOf()); + final Pointer bytes = malloc.allocate(nativeStructSize * sizeOf()); bytes.asTypedList(methods.length).setAll(0, methods); event.ref.bytes = bytes; event.ref.length = methods.length; + event.ref.is_custom_event = isCustomEvent ? 1 : 0; return event.cast(); } @@ -218,52 +229,86 @@ class Event { } } -class EventInit { - final bool bubbles; - final bool cancelable; - - EventInit({ - this.bubbles = false, - this.cancelable = false, - }); -} - class PopStateEvent extends Event { - final PopStateEventInit _popStateEventInit; - PopStateEvent(this._popStateEventInit) : super('popstate', _popStateEventInit); + final dynamic state; + + PopStateEvent({this.state}) : super(EVENT_POP_STATE); @override - Pointer toRaw([int methodLength = 0]) { - List methods = [stringToNativeString(jsonEncode(_popStateEventInit.state)).address]; + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { + List methods = [jsonEncode(state).toNativeUtf8().address]; - Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + Pointer rawEvent = super.toRaw(methods.length).cast(); + int currentStructSize = rawEvent.ref.length + methods.length; + Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); + rawEvent.ref.length = currentStructSize; return rawEvent; } } -class PopStateEventInit extends EventInit { - final dynamic state; - PopStateEventInit(this.state); -} +class UIEvent extends Event { + // Returns a long with details about the event, depending on the event type. + // For click or dblclick events, UIEvent.detail is the current click count. + // + // For mousedown or mouseup events, UIEvent.detail is 1 plus the current click count. + // + // For all other UIEvent objects, UIEvent.detail is always zero. + double detail; + + // The UIEvent.view read-only property returns the WindowProxy object from which the event was generated. In browsers, this is the Window object the event happened in. + EventTarget? view; + + // @Deprecated + // The UIEvent.which read-only property of the UIEvent interface returns a number that indicates which button was pressed on the mouse, or the numeric keyCode or the character code (charCode) of the key pressed on the keyboard. + double which; + + UIEvent( + String type, { + this.detail = 0.0, + this.view, + this.which = 0.0, + bool bubbles = false, + bool cancelable = false, + bool composed = false, + }) : super(type, bubbles: bubbles, cancelable: cancelable, composed: composed); -/// reference: https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent -class MouseEvent extends Event { - final MouseEventInit _mouseEventInit; + @override + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { + List methods = [doubleToUint64(detail), view?.pointer?.address ?? nullptr.address, doubleToUint64(which)]; - double get clientX => _mouseEventInit.clientX; - double get clientY => _mouseEventInit.clientY; - double get offsetX => _mouseEventInit.offsetX; - double get offsetY => _mouseEventInit.offsetY; + Pointer rawEvent = super.toRaw(methods.length + extraLength).cast(); + int currentStructSize = rawEvent.ref.length + methods.length; + Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); + bytes.setAll(rawEvent.ref.length, methods); + rawEvent.ref.length = currentStructSize; + + return rawEvent; + } +} - MouseEvent(String type, MouseEventInit mouseEventInit) - : _mouseEventInit = mouseEventInit, - super(type, mouseEventInit); +/// reference: https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent +class MouseEvent extends UIEvent { + double clientX; + double clientY; + double offsetX; + double offsetY; + + MouseEvent( + String type, { + this.clientX = 0.0, + this.clientY = 0.0, + this.offsetX = 0.0, + this.offsetY = 0.0, + double detail = 0.0, + EventTarget? view, + double which = 0.0, + }) : super(type, + detail: detail, view: view, which: which, bubbles: true, cancelable: true, composed: false); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [ doubleToUint64(clientX), doubleToUint64(clientY), @@ -271,31 +316,18 @@ class MouseEvent extends Event { doubleToUint64(offsetY) ]; - Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + Pointer rawEvent = super.toRaw(methods.length + extraLength).cast(); + int currentStructSize = rawEvent.ref.length + methods.length; + Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); + rawEvent.ref.length = currentStructSize; return rawEvent; } } -class MouseEventInit extends EventInit { - final double clientX; - final double clientY; - final double offsetX; - final double offsetY; - - MouseEventInit({ - bool bubbles = false, - bool cancelable = false, - this.clientX = 0.0, - this.clientY = 0.0, - this.offsetX = 0.0, - this.offsetY = 0.0, - }) : super(bubbles: bubbles, cancelable: cancelable); -} - -class GestureEventInit extends EventInit { +/// reference: https://developer.mozilla.org/en-US/docs/Web/API/GestureEvent +class GestureEvent extends Event { final String state; final String direction; final double rotation; @@ -305,9 +337,8 @@ class GestureEventInit extends EventInit { final double velocityY; final double scale; - GestureEventInit({ - bool bubbles = false, - bool cancelable = false, + GestureEvent( + String type, { this.state = '', this.direction = '', this.rotation = 0.0, @@ -316,28 +347,13 @@ class GestureEventInit extends EventInit { this.velocityX = 0.0, this.velocityY = 0.0, this.scale = 0.0, - }) : super(bubbles: bubbles, cancelable: cancelable); -} - -/// reference: https://developer.mozilla.org/en-US/docs/Web/API/GestureEvent -class GestureEvent extends Event { - final GestureEventInit _gestureEventInit; - - String get state => _gestureEventInit.state; - String get direction => _gestureEventInit.direction; - double get rotation => _gestureEventInit.rotation; - double get deltaX => _gestureEventInit.deltaX; - double get deltaY => _gestureEventInit.deltaY; - double get velocityX => _gestureEventInit.velocityX; - double get velocityY => _gestureEventInit.velocityY; - double get scale => _gestureEventInit.scale; - - GestureEvent(String type, GestureEventInit gestureEventInit) - : _gestureEventInit = gestureEventInit, - super(type, gestureEventInit); + bool bubbles = false, + bool cancelable = false, + bool composed = false, + }) : super(type, bubbles: bubbles, cancelable: cancelable, composed: composed); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [ stringToNativeString(state).address, stringToNativeString(direction).address, @@ -349,45 +365,49 @@ class GestureEvent extends Event { doubleToUint64(rotation) ]; - Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + Pointer rawEvent = super.toRaw(methods.length).cast(); + int currentStructSize = rawEvent.ref.length + methods.length; + Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); + rawEvent.ref.length = currentStructSize; return rawEvent; } } -class CustomEventInit extends EventInit { - final String detail; - - CustomEventInit({bool bubbles = false, bool cancelable = false, required this.detail}) - : super(bubbles: bubbles, cancelable: cancelable); -} - /// reference: http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html#interface-CustomEvent /// Attention: Detail now only can be a string. class CustomEvent extends Event { - final CustomEventInit _customEventInit; - String get detail => _customEventInit.detail; + dynamic detail; - CustomEvent(String type, CustomEventInit customEventInit) - : _customEventInit = customEventInit, - super(type, customEventInit); + CustomEvent( + String type, { + this.detail = null, + bool bubbles = false, + bool cancelable = false, + bool composed = false, + }) : super(type, bubbles: bubbles, cancelable: cancelable, composed: composed); @override - Pointer toRaw([int methodLength = 0]) { - List methods = [stringToNativeString(detail).address]; + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { + Pointer detailValue = malloc.allocate(sizeOf()); + toNativeValue(detailValue, detail); + List methods = [ + detailValue.address + ]; - Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + Pointer rawEvent = super.toRaw(methods.length, true).cast(); + int currentStructSize = rawEvent.ref.length + methods.length; + Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); + rawEvent.ref.length = currentStructSize; return rawEvent; } } // https://w3c.github.io/input-events/ -class InputEvent extends Event { +class InputEvent extends UIEvent { // A String containing the type of input that was made. // There are many possible values, such as insertText, // deleteContentBackward, insertFromPaste, and formatBold. @@ -395,20 +415,25 @@ class InputEvent extends Event { final String data; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [stringToNativeString(inputType).address, stringToNativeString(data).address]; - Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + Pointer rawEvent = super.toRaw(methods.length).cast(); + int currentStructSize = rawEvent.ref.length + methods.length; + Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); + rawEvent.ref.length = currentStructSize; return rawEvent; } - InputEvent( - this.data, { + InputEvent({ this.inputType = '', - }) : super(EVENT_INPUT, EventInit(cancelable: true)); + this.data = '', + bool bubbles = false, + bool cancelable = false, + bool composed = false, + }) : super(EVENT_INPUT, bubbles: bubbles, cancelable: cancelable, composed: composed); } class AppearEvent extends Event { @@ -427,10 +452,13 @@ class ColorSchemeChangeEvent extends Event { class MediaErrorCode { // The fetching of the associated resource was aborted by the user's request. static const double MEDIA_ERR_ABORTED = 1; + // Some kind of network error occurred which prevented the media from being successfully fetched, despite having previously been available. static const double MEDIA_ERR_NETWORK = 2; + // Despite having previously been determined to be usable, an error occurred while trying to decode the media resource, resulting in an error. static const double MEDIA_ERR_DECODE = 3; + // The associated resource or media provider object (such as a MediaStream) has been found to be unsuitable. static const double MEDIA_ERR_SRC_NOT_SUPPORTED = 4; } @@ -446,12 +474,14 @@ class MediaError extends Event { final String message; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [code, stringToNativeString(message).address]; - Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + Pointer rawEvent = super.toRaw(methods.length).cast(); + int currentStructSize = rawEvent.ref.length + methods.length; + Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); + rawEvent.ref.length = currentStructSize; return rawEvent; } @@ -466,16 +496,27 @@ class MessageEvent extends Event { /// A USVString representing the origin of the message emitter. final String origin; + final String lastEventId; + final String source; - MessageEvent(this.data, {this.origin = ''}) : super(EVENT_MESSAGE); + MessageEvent(this.data, {this.origin = '', this.lastEventId = '', this.source = ''}) : super(EVENT_MESSAGE); @override - Pointer toRaw([int methodLength = 0]) { - List methods = [stringToNativeString(jsonEncode(data)).address, stringToNativeString(origin).address]; + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { + Pointer nativeData = malloc.allocate(sizeOf()); + toNativeValue(nativeData, data); + List methods = [ + nativeData.address, + stringToNativeString(origin).address, + stringToNativeString(lastEventId).address, + stringToNativeString(source).address + ]; - Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + Pointer rawEvent = super.toRaw(methods.length).cast(); + int currentStructSize = rawEvent.ref.length + methods.length; + Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); + rawEvent.ref.length = currentStructSize; return rawEvent; } @@ -495,12 +536,14 @@ class CloseEvent extends Event { CloseEvent(this.code, this.reason, this.wasClean) : super(EVENT_CLOSE); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [code, stringToNativeString(reason).address, wasClean ? 1 : 0]; - Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + Pointer rawEvent = super.toRaw(methods.length).cast(); + int currentStructSize = rawEvent.ref.length + methods.length; + Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); + rawEvent.ref.length = currentStructSize; return rawEvent; } @@ -511,25 +554,38 @@ class IntersectionChangeEvent extends Event { final double intersectionRatio; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [doubleToUint64(intersectionRatio)]; - Pointer rawEvent = - super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + Pointer rawEvent = super.toRaw(methods.length).cast(); + int currentStructSize = rawEvent.ref.length + methods.length; + Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); + rawEvent.ref.length = currentStructSize; return rawEvent; } } /// reference: https://w3c.github.io/touch-events/#touchevent-interface -class TouchEvent extends Event { - TouchEvent(String type) : super(type, EventInit(bubbles: true, cancelable: true)); - - TouchList touches = TouchList(); - TouchList targetTouches = TouchList(); - TouchList changedTouches = TouchList(); +class TouchEvent extends UIEvent { + TouchEvent( + String type, { + TouchList? touches, + TouchList? targetTouches, + TouchList? changedTouches, + this.altKey = false, + this.metaKey = false, + this.ctrlKey = false, + this.shiftKey = false, + }) : touches = touches ?? TouchList(), + targetTouches = targetTouches ?? TouchList(), + changedTouches = changedTouches ?? TouchList(), + super(type, bubbles: true, cancelable: true, composed: true); + + TouchList touches; + TouchList targetTouches; + TouchList changedTouches; bool altKey = false; bool metaKey = false; @@ -537,23 +593,22 @@ class TouchEvent extends Event { bool shiftKey = false; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [ touches.toNative().address, - touches.length, targetTouches.toNative().address, - targetTouches.length, changedTouches.toNative().address, - changedTouches.length, altKey ? 1 : 0, metaKey ? 1 : 0, ctrlKey ? 1 : 0, shiftKey ? 1 : 0 ]; - Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + Pointer rawEvent = super.toRaw(methods.length).cast(); + int currentStructSize = rawEvent.ref.length + methods.length; + Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); + rawEvent.ref.length = currentStructSize; return rawEvent; } @@ -580,7 +635,6 @@ class Touch { final double force; final double altitudeAngle; final double azimuthAngle; - final TouchType touchType; const Touch({ required this.identifier, @@ -597,11 +651,9 @@ class Touch { this.force = 0, this.altitudeAngle = 0, this.azimuthAngle = 0, - this.touchType = TouchType.direct, }); - Pointer toNative() { - Pointer nativeTouch = malloc.allocate(sizeOf()); + void toNative(Pointer nativeTouch) { nativeTouch.ref.identifier = identifier; nativeTouch.ref.target = target.pointer!; nativeTouch.ref.clientX = clientX; @@ -616,14 +668,13 @@ class Touch { nativeTouch.ref.force = force; nativeTouch.ref.altitudeAngle = altitudeAngle; nativeTouch.ref.azimuthAngle = azimuthAngle; - nativeTouch.ref.touchType = touchType.index; - return nativeTouch; } } /// reference: https://w3c.github.io/touch-events/#touchlist-interface class TouchList { final List _items = []; + int get length => _items.length; Touch item(int index) { @@ -639,13 +690,14 @@ class TouchList { _items.add(touch); } - Pointer> toNative() { - Pointer> touchList = - malloc.allocate(sizeOf() * _items.length).cast>(); + Pointer toNative() { + Pointer touchList = malloc.allocate(sizeOf()); + Pointer touches = malloc.allocate(sizeOf() * _items.length); for (int i = 0; i < _items.length; i++) { - touchList[i] = _items[i].toNative(); + _items[i].toNative(touches.elementAt(i)); } - + touchList.ref.length = _items.length; + touchList.ref.touches = touches; return touchList; } } @@ -662,16 +714,18 @@ class AnimationEvent extends Event { String pseudoElement; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int extraLength = 0, bool isCustomEvent = false]) { List methods = [ stringToNativeString(animationName).address, doubleToUint64(elapsedTime), stringToNativeString(pseudoElement).address ]; - Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + Pointer rawEvent = super.toRaw(methods.length).cast(); + int currentStructSize = rawEvent.ref.length + methods.length; + Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); bytes.setAll(rawEvent.ref.length, methods); + rawEvent.ref.length = currentStructSize; return rawEvent; } diff --git a/webf/lib/src/dom/event_target.dart b/webf/lib/src/dom/event_target.dart index 839e65dec5..d02f76a9b7 100644 --- a/webf/lib/src/dom/event_target.dart +++ b/webf/lib/src/dom/event_target.dart @@ -23,6 +23,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; @@ -67,8 +68,12 @@ abstract class EventTarget extends BindingObject { event.currentTarget = this; // To avoid concurrent exception while prev handler modify the original handler list, causing list iteration // with error, copy the handlers here. - for (EventHandler handler in [...existHandler]) { - handler(event); + try { + for (EventHandler handler in [...existHandler]) { + handler(event); + } + } catch (e, stack) { + print('$e\n$stack'); } event.currentTarget = null; } diff --git a/webf/lib/src/dom/screen.dart b/webf/lib/src/dom/screen.dart index 80a639fd33..076d5f57d2 100644 --- a/webf/lib/src/dom/screen.dart +++ b/webf/lib/src/dom/screen.dart @@ -3,13 +3,13 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ import 'dart:ui'; - import 'package:webf/foundation.dart'; +import 'package:webf/bridge.dart'; // As its name suggests, the Screen interface represents information about the screen of the output device. // https://drafts.csswg.org/cssom-view/#the-screen-interface class Screen extends BindingObject { - Screen([BindingContext? context]) : super(context); + Screen(int contextId) : super(BindingContext(contextId, allocateNewBindingObject())); @override getBindingProperty(String key) { diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index 5c3ba5a1d0..836aa2a1f7 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -21,6 +21,8 @@ class StyleNodeManager { final List _pendingStyleSheets = []; bool get hasPendingStyleSheet => _pendingStyleSheets.isNotEmpty; + bool _isStyleSheetCandidateNodeChanged = false; + bool get isStyleSheetCandidateNodeChanged => _isStyleSheetCandidateNodeChanged; final Document document; @@ -32,6 +34,7 @@ class StyleNodeManager { } if (_styleSheetCandidateNodes.isEmpty) { _styleSheetCandidateNodes.add(node); + _isStyleSheetCandidateNodeChanged = true; return; } @@ -40,15 +43,18 @@ class StyleNodeManager { DocumentPosition position = _styleSheetCandidateNodes[i].compareDocumentPosition(node); if (position == DocumentPosition.FOLLOWING) { _styleSheetCandidateNodes.insert(i + 1, node); + _isStyleSheetCandidateNodeChanged = true; return; } } _styleSheetCandidateNodes.insert(0, node); + _isStyleSheetCandidateNodeChanged = true; } void removeStyleSheetCandidateNode(Node node) { _styleSheetCandidateNodes.remove(node); + _isStyleSheetCandidateNodeChanged = true; } void appendPendingStyleSheet(CSSStyleSheet styleSheet) { @@ -80,6 +86,7 @@ class StyleNodeManager { document.needsStyleRecalculate = true; document.handleStyleSheets(newSheets); _pendingStyleSheets.clear(); + _isStyleSheetCandidateNodeChanged = false; return true; } diff --git a/webf/lib/src/dom/window.dart b/webf/lib/src/dom/window.dart index e0b0afbb63..aa3dd2e943 100644 --- a/webf/lib/src/dom/window.dart +++ b/webf/lib/src/dom/window.dart @@ -17,7 +17,7 @@ class Window extends EventTarget { final Screen screen; Window(BindingContext? context, this.document) - : screen = Screen(context), + : screen = Screen(context!.contextId), super(context); @override @@ -92,8 +92,8 @@ class Window extends EventTarget { // including the size of a rendered scroll bar (if any), or zero if there is no viewport. // https://drafts.csswg.org/cssom-view/#dom-window-innerwidth // This is a read only idl attribute. - int get innerWidth => _viewportSize.width.toInt(); - int get innerHeight => _viewportSize.height.toInt(); + double get innerWidth => _viewportSize.width; + double get innerHeight => _viewportSize.height; Size get _viewportSize { RenderViewportBox? viewport = document.viewport; @@ -107,8 +107,10 @@ 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/webf/lib/src/foundation/binding.dart b/webf/lib/src/foundation/binding.dart index f053bc25e7..c817fb2b5d 100644 --- a/webf/lib/src/foundation/binding.dart +++ b/webf/lib/src/foundation/binding.dart @@ -2,13 +2,16 @@ * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +import 'dart:ffi'; +import 'dart:collection'; import 'package:flutter/foundation.dart'; +import 'package:webf/bridge.dart'; typedef BindingObjectOperation = void Function(BindingObject bindingObject); class BindingContext { final int contextId; - final pointer; + final Pointer pointer; const BindingContext(this.contextId, this.pointer); } @@ -19,12 +22,43 @@ abstract class BindingObject { final BindingContext? _context; int? get contextId => _context?.contextId; - get pointer => _context?.pointer; + Pointer? get pointer => _context?.pointer; BindingObject([BindingContext? context]) : _context = context { _bind(); } + int _functionId = 0; + final LinkedHashMap _functionMap = LinkedHashMap(); + final LinkedHashMap _asyncFunctionMap = LinkedHashMap(); + + AnonymousNativeFunction? getAnonymousNativeFunctionFromId(int id) { + return _functionMap[id]; + } + int setAnonymousNativeFunction(AnonymousNativeFunction fn) { + int newId = _functionId++; + _functionMap[newId] = fn; + + if (isEnabledLog) { + print('store native function for id: $newId bindingObject: $pointer'); + } + return newId; + } + + AsyncAnonymousNativeFunction? getAsyncAnonymousNativeFunctionFromId(int id) { + return _asyncFunctionMap[id]; + } + int setAsyncAnonymousNativeFunction(AsyncAnonymousNativeFunction fn) { + int newId = _functionId++; + _asyncFunctionMap[newId] = fn; + + if (isEnabledLog) { + print('store async native function for id: $newId bindingObject: $pointer'); + } + + return newId; + } + // Bind dart side object method to receive invoking from native side. void _bind() { if (bind != null) { @@ -40,11 +74,18 @@ abstract class BindingObject { // Get a property, eg: // console.log(el.foo); - dynamic getBindingProperty(String key) {} + dynamic getBindingProperty(String key) { + return null; + } // Set a property, eg: // el.foo = 'bar'; - void setBindingProperty(String key, value) {} + void setBindingProperty(String key, value) { + } + + // Return a list contains all supported properties. + void getAllBindingPropertyNames(List properties) { + } // Call a method, eg: // el.getContext('2x'); diff --git a/webf/lib/src/foundation/cookie_jar.dart b/webf/lib/src/foundation/cookie_jar.dart new file mode 100644 index 0000000000..885f3a9dda --- /dev/null +++ b/webf/lib/src/foundation/cookie_jar.dart @@ -0,0 +1,38 @@ +// Legacy implements. +// TODO: should read cookie values from http requests. +class CookieJar { + final Map _pairs = {}; + void setCookie(String value) { + value = value.trim(); + + RegExp pattern = RegExp(r'^[^=]*=([^;]*)'); + + if (!value.contains('=')) { + _pairs[''] = value; + } else { + int idx = value.indexOf('='); + String key = value.substring(0, idx); + // Only allow to set a single cookie at a time + // Find first cookie value if multiple cookie set + RegExpMatch? match = pattern.firstMatch(value); + + if (match != null && match[1] != null) { + value = match[1]!; + + if (key.isEmpty && value.isEmpty) { + return; + } + } + _pairs[key] = value; + } + } + + String cookie() { + List cookiePairs = List.generate(_pairs.length, (index) { + String key = _pairs.keys.elementAt(index); + String value = _pairs.values.elementAt(index); + return '$key=$value'; + }); + return cookiePairs.join('; '); + } +} diff --git a/webf/lib/src/gesture/gesture_dispatcher.dart b/webf/lib/src/gesture/gesture_dispatcher.dart index 4e60c39231..5f9d0caf3b 100644 --- a/webf/lib/src/gesture/gesture_dispatcher.dart +++ b/webf/lib/src/gesture/gesture_dispatcher.dart @@ -98,12 +98,14 @@ class GestureDispatcher { final Map _gestureRecognizers = {}; List _eventPath = const []; + // Collect the events in the event path list. final Map _eventsInPath = {}; final Throttling _throttler = Throttling(duration: Duration(milliseconds: _MAX_STEP_MS)); final Map _pointTargets = {}; + void _bindEventTargetWithTouchPoint(TouchPoint touchPoint, EventTarget eventTarget) { _pointTargets[touchPoint.id] = eventTarget; } @@ -129,6 +131,7 @@ class GestureDispatcher { } final Map _touchPoints = {}; + void _addPoint(TouchPoint touchPoint) { _touchPoints[touchPoint.id] = touchPoint; } @@ -311,13 +314,7 @@ class GestureDispatcher { return _dragEventInfo; } - void _handleMouseEvent( - String type, { - Offset localPosition = Offset.zero, - Offset globalPosition = Offset.zero, - bool bubbles = true, - bool cancelable = true, - }) { + void _handleMouseEvent(String type, {Offset localPosition = Offset.zero, Offset globalPosition = Offset.zero}) { if (_target == null) { return; } @@ -333,16 +330,12 @@ class GestureDispatcher { double clientX = globalOffset.dx; double clientY = globalOffset.dy; - Event event = MouseEvent( - type, - MouseEventInit( - bubbles: bubbles, - cancelable: cancelable, - clientX: clientX, - clientY: clientY, - offsetX: localPosition.dx, - offsetY: localPosition.dy, - )); + Event event = MouseEvent(type, + clientX: clientX, + clientY: clientY, + offsetX: localPosition.dx, + offsetY: localPosition.dy, + view: (_target as Node).ownerDocument.defaultView); _target?.dispatchEvent(event); } @@ -356,17 +349,16 @@ class GestureDispatcher { double velocityY = 0.0, double scale = 0.0}) { Event event = GestureEvent( - type, - GestureEventInit( - state: state, - direction: direction, - rotation: rotation, - deltaX: deltaX, - deltaY: deltaY, - velocityX: velocityX, - velocityY: velocityY, - scale: scale, - )); + type, + state: state, + direction: direction, + rotation: rotation, + deltaX: deltaX, + deltaY: deltaY, + velocityX: velocityX, + velocityY: velocityY, + scale: scale, + ); _target?.dispatchEvent(event); } diff --git a/webf/lib/src/launcher/controller.dart b/webf/lib/src/launcher/controller.dart index b7f2671095..a15a76d1ae 100644 --- a/webf/lib/src/launcher/controller.dart +++ b/webf/lib/src/launcher/controller.dart @@ -11,6 +11,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'dart:ui' as ui; +import 'package:ffi/ffi.dart'; import 'package:flutter/animation.dart'; import 'package:flutter/widgets.dart' show RenderObjectElement; import 'package:flutter/foundation.dart'; @@ -26,9 +27,6 @@ import 'package:webf/module.dart'; import 'package:webf/rendering.dart'; import 'package:webf/widget.dart'; -const int WINDOW_ID = -1; -const int DOCUMENT_ID = -2; - // Error handler when load bundle failed. typedef LoadHandler = void Function(WebFController controller); typedef LoadErrorHandler = void Function(FlutterError error, StackTrace stack); @@ -66,9 +64,6 @@ abstract class DevToolsService { // An kraken View Controller designed for multiple kraken view control. class WebFViewController implements WidgetsBindingObserver, ElementsBindingObserver { - static Map> documentNativePtrMap = {}; - static Map> windowNativePtrMap = {}; - WebFController rootController; // The methods of the KrakenNavigateDelegation help you implement custom behaviors that are triggered @@ -118,7 +113,7 @@ class WebFViewController implements WidgetsBindingObserver, ElementsBindingObser 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); @@ -143,18 +138,50 @@ class WebFViewController implements WidgetsBindingObserver, ElementsBindingObser defineBuiltInElements(); + // Wait viewport mounted on the outside renderObject tree. + Future.microtask(() { + // Execute UICommand.createDocument and UICommand.createWindow to initialize window and document. + flushUICommand(this); + }); + + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_ELEMENT_MANAGER_INIT_END); + } + + SchedulerBinding.instance.addPostFrameCallback(_postFrameCallback); + } + + void _postFrameCallback(Duration timeStamp) { + if (disposed) return; + flushUICommand(this); + SchedulerBinding.instance.addPostFrameCallback(_postFrameCallback); + } + + // Index value which identify javascript runtime context. + 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 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) { @@ -175,6 +202,14 @@ class WebFViewController implements WidgetsBindingObserver, ElementsBindingObser document.addEventListener(EVENT_DRAG, (Event event) => listener.onDrag!(event as GestureEvent)); } } + } + + void initWindow(int targetId, Pointer pointer) { + window = Window( + BindingContext(_contextId, pointer), + document); + _registerPlatformBrightnessChange(); + _setEventTarget(targetId, window); // Blur input element when new input focused. window.addEventListener(EVENT_CLICK, (event) { @@ -186,28 +221,8 @@ class WebFViewController implements WidgetsBindingObserver, ElementsBindingObser (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, 'WebF have already disposed'); evaluateScripts(_contextId, code); @@ -470,6 +485,8 @@ class WebFViewController implements WidgetsBindingObserver, ElementsBindingObser originalTarget.attributes.forEach((key, value) { newElement.setAttribute(key, value); }); + newElement.className = originalTarget.className; + newElement.id = originalTarget.id; } } @@ -673,12 +690,13 @@ class WebFViewController implements WidgetsBindingObserver, ElementsBindingObser } // Call from JS Bridge before JS side eventTarget object been Garbage collected. - void disposeEventTarget(int targetId) { + void disposeEventTarget(int targetId, Pointer pointer) { Node? target = _getEventTargetById(targetId); if (target == null) return; _removeTarget(targetId); target.dispose(); + malloc.free(pointer); } RenderObject getRootRenderObject() { @@ -960,7 +978,8 @@ class WebFController { _module.dispose(); _view.dispose(); - allocateNewPage(_view.contextId); + List methodBytes = makeDartMethodsData(); + allocateNewPage(methodBytes, _view.contextId); _view = WebFViewController(view.viewportWidth, view.viewportHeight, background: _view.background, diff --git a/webf/lib/src/launcher/launcher.dart b/webf/lib/src/launcher/launcher.dart index 8f799e2f9f..00f316515b 100644 --- a/webf/lib/src/launcher/launcher.dart +++ b/webf/lib/src/launcher/launcher.dart @@ -39,11 +39,8 @@ void launch( VoidCallback? _ordinaryOnMetricsChanged = window.onMetricsChanged; Future _initWebFApp() async { - WebFNativeChannel channel = WebFNativeChannel(); - if (bundle == null) { String? backendEntrypointUrl = getBundleURLFromEnv() ?? getBundlePathFromEnv(); - backendEntrypointUrl ??= await channel.getUrl(); if (backendEntrypointUrl != null) { bundle = WebFBundle.fromUrl(backendEntrypointUrl); } @@ -55,7 +52,6 @@ void launch( window.physicalSize.height / window.devicePixelRatio, background: background, showPerformanceOverlay: showPerformanceOverlay ?? Platform.environment[ENABLE_PERFORMANCE_OVERLAY] != null, - methodChannel: channel, entrypoint: bundle, devToolsService: devToolsService, httpClientInterceptor: httpClientInterceptor, diff --git a/webf/lib/src/module/history.dart b/webf/lib/src/module/history.dart index fa0c2a4379..985d650aaa 100644 --- a/webf/lib/src/module/history.dart +++ b/webf/lib/src/module/history.dart @@ -107,8 +107,7 @@ class HistoryModule extends BaseModule { } void _dispatchPopStateEvent(state) { - PopStateEventInit init = PopStateEventInit(state); - PopStateEvent popStateEvent = PopStateEvent(init); + PopStateEvent popStateEvent = PopStateEvent(state: state); moduleManager!.controller.view.window.dispatchEvent(popStateEvent); } diff --git a/webf/lib/src/module/method_channel.dart b/webf/lib/src/module/method_channel.dart index d314991f5e..e00c85713a 100644 --- a/webf/lib/src/module/method_channel.dart +++ b/webf/lib/src/module/method_channel.dart @@ -5,10 +5,9 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; import 'package:webf/webf.dart'; -typedef MethodCallCallback = Future Function(String method, Object? arguments); +typedef MethodCallCallback = Future Function(String method, List arguments); const String METHOD_CHANNEL_NOT_INITIALIZED = 'MethodChannel not initialized.'; const String CONTROLLER_NOT_INITIALIZED = 'WebF controller not initialized.'; const String METHOD_CHANNEL_NAME = 'MethodChannel'; @@ -22,7 +21,7 @@ class MethodChannelModule extends BaseModule { void dispose() {} @override - String invoke(String method, params, callback) { + dynamic invoke(String method, params, callback) { if (method == 'invokeMethod') { _invokeMethodFromJavaScript(moduleManager!.controller, params[0], params[1]).then((result) { callback(data: result); @@ -34,18 +33,6 @@ class MethodChannelModule extends BaseModule { } } -void setJSMethodCallCallback(WebFController controller) { - if (controller.methodChannel == null) return; - - controller.methodChannel!._onJSMethodCall = (String method, arguments) async { - try { - controller.module.moduleManager.emitModuleEvent(METHOD_CHANNEL_NAME, data: [method, arguments]); - } catch (e, stack) { - print('Error invoke module event: $e, $stack'); - } - }; -} - abstract class WebFMethodChannel { MethodCallCallback? _onJSMethodCallCallback; @@ -58,7 +45,11 @@ abstract class WebFMethodChannel { static void setJSMethodCallCallback(WebFController controller) { controller.methodChannel?._onJSMethodCall = (String method, arguments) async { - controller.module.moduleManager.emitModuleEvent(METHOD_CHANNEL_NAME, data: [method, arguments]); + try { + return controller.module.moduleManager.emitModuleEvent(METHOD_CHANNEL_NAME, data: [method, arguments]); + } catch (e, stack) { + print('Error invoke module event: $e, $stack'); + } }; } } @@ -93,54 +84,6 @@ class WebFJavaScriptChannel extends WebFMethodChannel { } } -class WebFNativeChannel extends WebFMethodChannel { - // Flutter method channel used to communicate with public SDK API - // Only works when integration wieh public SDK API - - static final MethodChannel _nativeChannel = getWebFMethodChannel() - ..setMethodCallHandler((call) async { - String method = call.method; - WebFController? controller = WebFController.getControllerOfJSContextId(0); - - if (controller == null) return; - - if ('reload' == method) { - await controller.reload(); - } else if (controller.methodChannel!._onJSMethodCallCallback != null) { - return controller.methodChannel!._onJSMethodCallCallback!(method, call.arguments); - } - - return Future.value(null); - }); - - @override - Future invokeMethodFromJavaScript(String method, List arguments) async { - Map argsWrap = { - 'method': method, - 'args': arguments, - }; - return _nativeChannel.invokeMethod('invokeMethod', argsWrap); - } - - Future getUrl() async { - // Maybe url of zip bundle or js bundle - String? url = await _nativeChannel.invokeMethod('getUrl'); - - // @NOTE(zhuoling.lcl): Android plugin protocol cannot return `null` directly, which - // will case method channel invoke failed with exception, use empty - // string to represent null value. - if (url != null && url.isEmpty) url = null; - return url; - } - - static Future syncDynamicLibraryPath() async { - String? path = await _nativeChannel.invokeMethod('getDynamicLibraryPath'); - if (path != null) { - WebFDynamicLibrary.dynamicLibraryPath = path; - } - } -} - Future _invokeMethodFromJavaScript(WebFController? controller, String method, List args) { WebFMethodChannel? webFMethodChannel = controller?.methodChannel; if (webFMethodChannel != null) { diff --git a/webf/lib/src/module/module_manager.dart b/webf/lib/src/module/module_manager.dart index f1ad8ec966..09e3d40cd8 100644 --- a/webf/lib/src/module/module_manager.dart +++ b/webf/lib/src/module/module_manager.dart @@ -2,8 +2,6 @@ * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import 'dart:convert'; - import 'package:webf/bridge.dart' as bridge; import 'package:webf/dom.dart'; import 'package:webf/webf.dart'; @@ -12,11 +10,14 @@ abstract class BaseModule { String get name; final ModuleManager? moduleManager; BaseModule(this.moduleManager); - String invoke(String method, params, InvokeModuleCallback callback); + dynamic invoke(String method, params, InvokeModuleCallback callback); + dynamic dispatchEvent({Event? event, data}) { + return moduleManager!.emitModuleEvent(name, event: event, data: data); + } void dispose(); } -typedef InvokeModuleCallback = void Function({String? error, Object? data}); +typedef InvokeModuleCallback = Future Function({String? error, Object? data}); typedef NewModuleCreator = BaseModule Function(ModuleManager); typedef ModuleCreator = BaseModule Function(ModuleManager? moduleManager); @@ -27,6 +28,7 @@ class ModuleManager { static final Map _creatorMap = {}; static bool inited = false; final Map _moduleMap = {}; + bool disposed = false; ModuleManager(this.controller, this.contextId) { if (!inited) { @@ -61,11 +63,11 @@ class ModuleManager { _creatorMap[fakeModule.name] = moduleCreator; } - void emitModuleEvent(String moduleName, {Event? event, Object? data}) { - bridge.emitModuleEvent(contextId, moduleName, event, jsonEncode(data)); + dynamic emitModuleEvent(String moduleName, {Event? event, data}) { + return bridge.emitModuleEvent(contextId, moduleName, event, data); } - String invokeModule(String moduleName, String method, params, InvokeModuleCallback callback) { + dynamic invokeModule(String moduleName, String method, params, InvokeModuleCallback callback) { ModuleCreator? creator = _creatorMap[moduleName]; if (creator == null) { throw Exception('ModuleManager: Can not find module of name: $moduleName'); @@ -76,10 +78,16 @@ class ModuleManager { } BaseModule module = _moduleMap[moduleName]!; - return module.invoke(method, params, callback); + return module.invoke(method, params, ({String? error, Object? data}) async { + if (disposed) { + return null; + } + return callback(error: error, data: data); + }); } void dispose() { + disposed = true; _moduleMap.forEach((key, module) { module.dispose(); }); diff --git a/webf/lib/src/rendering/box_model.dart b/webf/lib/src/rendering/box_model.dart index 45d18cb4cf..121968afdd 100644 --- a/webf/lib/src/rendering/box_model.dart +++ b/webf/lib/src/rendering/box_model.dart @@ -1048,16 +1048,16 @@ class RenderBoxModel extends RenderBox return _contentSize ?? Size.zero; } - int get clientWidth { + double get clientWidth { double width = contentSize.width; width += renderStyle.padding.horizontal; - return width.toInt(); + return width; } - int get clientHeight { + double get clientHeight { double height = contentSize.height; height += renderStyle.padding.vertical; - return height.toInt(); + return height; } // Base layout methods to compute content constraints before content box layout. diff --git a/webf/lib/src/widget/webf.dart b/webf/lib/src/widget/webf.dart index 70dfe16bbc..a6a9828c35 100644 --- a/webf/lib/src/widget/webf.dart +++ b/webf/lib/src/widget/webf.dart @@ -199,11 +199,6 @@ class WebFRenderObjectWidget extends SingleChildRenderObjectWidget { double viewportWidth = _webfWidget.viewportWidth ?? window.physicalSize.width / window.devicePixelRatio; double viewportHeight = _webfWidget.viewportHeight ?? window.physicalSize.height / window.devicePixelRatio; - if (viewportWidth == 0.0 && viewportHeight == 0.0) { - throw FlutterError('''Can't get viewportSize from window. Please set viewportWidth and viewportHeight manually. -This situation often happened when you trying creating webf when FlutterView not initialized.'''); - } - WebFController controller = WebFController(shortHash(_webfWidget.hashCode), viewportWidth, viewportHeight, background: _webfWidget.background, showPerformanceOverlay: Platform.environment[ENABLE_PERFORMANCE_OVERLAY] != null, @@ -226,6 +221,33 @@ This situation often happened when you trying creating webf when FlutterView not onControllerCreated(controller); } + if (viewportWidth == 0.0 && viewportHeight == 0.0) { + // window.physicalSize are Size.zero when app first loaded. This only happened on Android and iOS physical devices with release build. + // We should wait for onMetricsChanged when window.physicalSize get updated from Flutter Engine. + VoidCallback? _ordinaryOnMetricsChanged = window.onMetricsChanged; + window.onMetricsChanged = () async { + if (window.physicalSize == Size.zero) { + return; + } + + double viewportWidth = _webfWidget.viewportWidth ?? window.physicalSize.width / window.devicePixelRatio; + double viewportHeight = _webfWidget.viewportHeight ?? window.physicalSize.height / window.devicePixelRatio; + + controller.view.viewportWidth = viewportWidth; + controller.view.document.documentElement!.renderStyle.width = CSSLengthValue(viewportWidth, CSSLengthType.PX); + + controller.view.viewportHeight = viewportHeight; + controller.view.document.documentElement!.renderStyle.height = CSSLengthValue(viewportHeight, CSSLengthType.PX); + + // Should proxy to ordinary window.onMetricsChanged callbacks. + if (_ordinaryOnMetricsChanged != null) { + _ordinaryOnMetricsChanged(); + // Recover ordinary callback to window.onMetricsChanged + window.onMetricsChanged = _ordinaryOnMetricsChanged; + } + }; + } + if (kProfileMode) { PerformanceTiming.instance().mark(PERF_CONTROLLER_INIT_END); } diff --git a/webf/macos/Classes/WebF.h b/webf/macos/Classes/WebF.h deleted file mode 100644 index 5a68fccaf3..0000000000 --- a/webf/macos/Classes/WebF.h +++ /dev/null @@ -1,31 +0,0 @@ -#import -#import "WebF.h" -#import "WebFPlugin.h" - -typedef void(^MethodHandler)(FlutterMethodCall* _Nonnull , FlutterResult _Nonnull); - -@interface WebF : NSObject - -+ (WebF* _Nonnull) instanceByBinaryMessenger: (NSObject* _Nonnull) messenger; - -@property NSString* _Nullable bundleUrl; -@property FlutterEngine* _Nonnull flutterEngine; -@property FlutterMethodChannel* _Nullable channel; -@property MethodHandler _Nullable methodHandler; - -- (instancetype _Nonnull)initWithFlutterEngine: (FlutterEngine* _Nonnull) engine; - -- (NSString* _Nullable) getUrl; - -- (void) loadUrl: (NSString* _Nonnull)url; - -- (void) reload; - -- (void) reloadWithUrl: (NSString* _Nonnull) url; - -- (void) registerMethodCallHandler: (MethodHandler _Nonnull) handler; - -- (void) invokeMethod: (NSString* _Nonnull)method arguments:(nullable id) arguments; - -- (void) _handleMethodCall:(FlutterMethodCall* _Nonnull)call result:(FlutterResult _Nonnull )result; -@end diff --git a/webf/macos/Classes/WebF.m b/webf/macos/Classes/WebF.m deleted file mode 100644 index 18d648504b..0000000000 --- a/webf/macos/Classes/WebF.m +++ /dev/null @@ -1,86 +0,0 @@ -#import -#import "WebF.h" -#import "WebFPlugin.h" - -static NSMutableArray *engineList = nil; -static NSMutableArray *instanceList = nil; - -@implementation WebF - -+ (WebF*) instanceByBinaryMessenger: (NSObject*) messenger { - // Return last instance, multi instance not supported yet. - if (instanceList != nil && instanceList.count > 0) { - return [instanceList objectAtIndex: instanceList.count - 1]; - } - return nil; -} - -- (instancetype)initWithFlutterEngine: (FlutterEngine*) engine { - self.flutterEngine = engine; - - FlutterMethodChannel *channel = [WebFPlugin getMethodChannel]; - - if (channel == nil) { - NSException* exception = [NSException - exceptionWithName:@"InitError" - reason:@"WebFSDK should init after Flutter's plugin registered." - userInfo:nil]; - @throw exception; - } - self.channel = channel; - - if (engineList == nil) { - engineList = [[NSMutableArray alloc] initWithCapacity: 0]; - } - [engineList addObject: engine]; - - if (instanceList == nil) { - instanceList = [[NSMutableArray alloc] initWithCapacity: 0]; - } - [instanceList addObject: self]; - - return self; -} - -- (void) loadUrl:(NSString*)url { - if (url != nil) { - self.bundleUrl = url; - } -} - -- (void) reload { - if (self.channel != nil) { - [self.channel invokeMethod:@"reload" arguments:nil]; - } -} - -- (void) reloadWithUrl: (NSString*) url { - [self loadUrl: url]; - [self reload]; -} - -- (NSString*) getUrl { - return self.bundleUrl; -} - -- (void) invokeMethod:(NSString *)method arguments:(nullable id) arguments { - dispatch_async(dispatch_get_main_queue(), ^{ - if (self.channel != nil) { - [self.channel invokeMethod:method arguments:arguments]; - } - }); -} - -- (void) _handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - if (self.methodHandler != nil) { - self.methodHandler(call, result); - } -} - -- (void) registerMethodCallHandler: (MethodHandler) handler { - self.methodHandler = handler; -} - -@end - - diff --git a/webf/macos/Classes/WebFPlugin.h b/webf/macos/Classes/WebFPlugin.h index 7697dd993e..853d5696cb 100644 --- a/webf/macos/Classes/WebFPlugin.h +++ b/webf/macos/Classes/WebFPlugin.h @@ -1,7 +1,5 @@ #import -#define NAME_METHOD_SPLIT @"!!" - @interface WebFPlugin : NSObject @property NSObject *registrar; diff --git a/webf/macos/Classes/WebFPlugin.m b/webf/macos/Classes/WebFPlugin.m index 5ed0f2909d..133c3a1328 100644 --- a/webf/macos/Classes/WebFPlugin.m +++ b/webf/macos/Classes/WebFPlugin.m @@ -1,4 +1,3 @@ -#import "WebF.h" #import "WebFPlugin.h" static FlutterMethodChannel *methodChannel = nil; @@ -27,18 +26,7 @@ - (instancetype) initWithRegistrar: (NSObject*)registrar } - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - if ([@"getUrl" isEqualToString:call.method]) { - WebF* krakenInstance = [WebF instanceByBinaryMessenger: [self.registrar messenger]]; - if (krakenInstance != nil) { - result([krakenInstance getUrl]); - } else { - result(nil); - } - } else if ([@"invokeMethod" isEqualToString: call.method]) { - WebF* krakenInstance = [WebF instanceByBinaryMessenger: [self.registrar messenger]]; - FlutterMethodCall* callWrap = [FlutterMethodCall methodCallWithMethodName: call.arguments[@"method"] arguments: call.arguments[@"args"]]; - [krakenInstance _handleMethodCall:callWrap result:result]; - } else if ([@"getTemporaryDirectory" isEqualToString: call.method]) { + if ([@"getTemporaryDirectory" isEqualToString: call.method]) { result([self getTemporaryDirectory]); } else { result(FlutterMethodNotImplemented); diff --git a/webf/pubspec.yaml b/webf/pubspec.yaml index f7018c58c0..524ca88608 100644 --- a/webf/pubspec.yaml +++ b/webf/pubspec.yaml @@ -1,11 +1,11 @@ name: webf description: A W3C standard compliant Web rendering engine based on Flutter. -version: 0.12.0+2 +version: 0.13.0-beta.3 homepage: https://openwebf.com environment: sdk: ">=2.17.5 <3.0.0" - flutter: ">=3.0.2 <=3.0.5" + flutter: ">=3.0.5" dependencies: flutter: @@ -14,6 +14,7 @@ dependencies: meta: ^1.7.0 # Pure dart module. ffi: ^2.0.1 # Pure dart module. characters: ^1.2.0 + collection: ^1.16.0 async: ^2.8.2 # Pure dart module. quiver: ^3.1.0 # Pure dart module. vector_math: ^2.1.2 # Pure dart module.